MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Globalization;8using System.IO;9using System.Management.Automation.Configuration;10using System.Management.Automation.Internal;11using System.Management.Automation.Runspaces;12using System.Security;13using System.Text;14using System.Threading;15using System.Threading.Tasks;16 17namespace System.Management.Automation.Host18{19 /// <summary>20 /// Defines the properties and facilities providing by an hosting application deriving from21 /// <see cref="System.Management.Automation.Host.PSHost"/> that offers dialog-oriented and22 /// line-oriented interactive features.23 /// </summary>24 /// <seealso cref="System.Management.Automation.Host.PSHost"/>25 /// <seealso cref="System.Management.Automation.Host.PSHostRawUserInterface"/>26 public abstract class PSHostUserInterface27 {28 /// <summary>29 /// Gets hosting application's implementation of the30 /// <see cref="System.Management.Automation.Host.PSHostRawUserInterface"/> abstract base class31 /// that implements that class.32 /// </summary>33 /// <value>34 /// A reference to an instance of the hosting application's implementation of a class derived from35 /// <see cref="System.Management.Automation.Host.PSHostUserInterface"/>, or null to indicate that36 /// low-level user interaction is not supported.37 /// </value>38 public abstract System.Management.Automation.Host.PSHostRawUserInterface RawUI39 {40 get;41 }42 43 /// <summary>44 /// Returns true for hosts that support VT100 like virtual terminals.45 /// </summary>46 public virtual bool SupportsVirtualTerminal { get { return false; } }47 48 #region Line-oriented interaction49 /// <summary>50 /// Reads characters from the console until a newline (a carriage return) is encountered.51 /// </summary>52 /// <returns>53 /// The characters typed by the user.54 /// </returns>55 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLineAsSecureString"/>56 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string)"/>57 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string, System.Management.Automation.PSCredentialTypes, System.Management.Automation.PSCredentialUIOptions)"/>58 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForChoice"/>59 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Prompt"/>60 public abstract string ReadLine();61 62 /// <summary>63 /// Same as ReadLine, except that the result is a SecureString, and that the input is not echoed to the user while it is64 /// collected (or is echoed in some obfuscated way, such as showing a dot for each character).65 /// </summary>66 /// <returns>67 /// The characters typed by the user in an encrypted form.68 /// </returns>69 /// <remarks>70 /// Note that credentials (a user name and password) should be gathered with71 /// <see cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string)"/>72 /// <see cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string, System.Management.Automation.PSCredentialTypes, System.Management.Automation.PSCredentialUIOptions)"/>73 /// </remarks>74 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLine"/>75 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string)"/>76 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string, System.Management.Automation.PSCredentialTypes, System.Management.Automation.PSCredentialUIOptions)"/>77 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForChoice"/>78 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Prompt"/>79 public abstract SecureString ReadLineAsSecureString();80 81 /// <summary>82 /// Writes characters to the screen buffer. Does not append a carriage return.83 /// <!-- Here we choose to just offer string parameters rather than the 18 overloads from TextWriter -->84 /// </summary>85 /// <param name="value">86 /// The characters to be written. null is not allowed.87 /// </param>88 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Write(ConsoleColor, ConsoleColor, string)"/>89 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine()"/>90 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(string)"/>91 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(System.ConsoleColor, System.ConsoleColor, string)"/>92 public abstract void Write(string value);93 94 /// <summary>95 /// Same as <see cref="System.Management.Automation.Host.PSHostUserInterface.Write(string)"/>,96 /// except that colors can be specified.97 /// </summary>98 /// <param name="foregroundColor">99 /// The foreground color to display the text with.100 /// </param>101 /// <param name="backgroundColor">102 /// The foreground color to display the text with.103 /// </param>104 /// <param name="value">105 /// The characters to be written. null is not allowed.106 /// </param>107 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Write(string)"/>108 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine()"/>109 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(string)"/>110 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(System.ConsoleColor, System.ConsoleColor, string)"/>111 public abstract void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value);112 113 /// <summary>114 /// The default implementation writes a carriage return to the screen buffer.115 /// <see cref="System.Management.Automation.Host.PSHostUserInterface.Write(string)"/>116 /// <see cref="System.Management.Automation.Host.PSHostUserInterface.Write(System.ConsoleColor, System.ConsoleColor, string)"/>117 /// <see cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(string)"/>118 /// <see cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(System.ConsoleColor, System.ConsoleColor, string)"/>119 /// </summary>120 public virtual void WriteLine()121 {122 WriteLine(string.Empty);123 }124 125 /// <summary>126 /// Writes characters to the screen buffer, and appends a carriage return.127 /// </summary>128 /// <param name="value">129 /// The characters to be written. null is not allowed.130 /// </param>131 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Write(string)"/>132 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Write(System.ConsoleColor, System.ConsoleColor, string)"/>133 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine()"/>134 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(System.ConsoleColor, System.ConsoleColor, string)"/>135 public abstract void WriteLine(string value);136 137 /// <summary>138 /// Same as <see cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(string)"/>,139 /// except that colors can be specified.140 /// </summary>141 /// <param name="foregroundColor">142 /// The foreground color to display the text with.143 /// </param>144 /// <param name="backgroundColor">145 /// The foreground color to display the text with.146 /// </param>147 /// <param name="value">148 /// The characters to be written. null is not allowed.149 /// </param>150 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Write(string)"/>151 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Write(System.ConsoleColor, System.ConsoleColor, string)"/>152 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine()"/>153 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(string)"/>154 public virtual void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)155 {156 // #pragma warning disable 56506157 158 // expressly not checking for value == null so that attempts to write a null cause an exception159 160 if ((value != null) && (value.Length != 0))161 {162 Write(foregroundColor, backgroundColor, value);163 }164 165 Write("\n");166 167 // #pragma warning restore 56506168 }169 170 /// <summary>171 /// Writes a line to the "error display" of the host, as opposed to the "output display," which is172 /// written to by the variants of173 /// <see cref="System.Management.Automation.Host.PSHostUserInterface.Write(string)"/>174 /// <see cref="System.Management.Automation.Host.PSHostUserInterface.Write(System.ConsoleColor, System.ConsoleColor, string)"/>175 /// <see cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine()"/> and176 /// <see cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(string)"/>177 /// </summary>178 /// <param name="value">179 /// The characters to be written.180 /// </param>181 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Write(string)"/>182 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Write(System.ConsoleColor, System.ConsoleColor, string)"/>183 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine()"/>184 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteLine(string)"/>185 public abstract void WriteErrorLine(string value);186 187 /// <summary>188 /// Invoked by <see cref="System.Management.Automation.Cmdlet.WriteDebug"/> to display a debugging message189 /// to the user.190 /// </summary>191 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteProgress"/>192 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteVerboseLine"/>193 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteWarningLine"/>194 public abstract void WriteDebugLine(string message);195 196 /// <summary>197 /// Invoked by <see cref="System.Management.Automation.Cmdlet.WriteProgress(Int64, System.Management.Automation.ProgressRecord)"/> to display a progress record.198 /// </summary>199 /// <param name="sourceId">200 /// Unique identifier of the source of the record. An int64 is used because typically, the 'this' pointer of201 /// the command from whence the record is originating is used, and that may be from a remote Runspace on a 64-bit202 /// machine.203 /// </param>204 /// <param name="record">205 /// The record being reported to the host.206 /// </param>207 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteDebugLine"/>208 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteVerboseLine"/>209 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteWarningLine"/>210 public abstract void WriteProgress(Int64 sourceId, ProgressRecord record);211 212 /// <summary>213 /// Invoked by <see cref="System.Management.Automation.Cmdlet.WriteVerbose"/> to display a verbose processing message to the user.214 /// </summary>215 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteDebugLine"/>216 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteProgress"/>217 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteWarningLine"/>218 public abstract void WriteVerboseLine(string message);219 220 /// <summary>221 /// Invoked by <see cref="System.Management.Automation.Cmdlet.WriteWarning"/> to display a warning processing message to the user.222 /// </summary>223 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteDebugLine"/>224 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteProgress"/>225 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.WriteVerboseLine"/>226 public abstract void WriteWarningLine(string message);227 228 /// <summary>229 /// Invoked by <see cref="System.Management.Automation.Cmdlet.WriteInformation(InformationRecord)"/> to give the host a chance to intercept230 /// informational messages. These should not be displayed to the user by default, but may be useful to display in231 /// a separate area of the user interface.232 /// </summary>233 public virtual void WriteInformation(InformationRecord record) { }234 235 private static bool ShouldOutputPlainText(bool isHost, bool? supportsVirtualTerminal)236 {237 var outputRendering = OutputRendering.PlainText;238 239 if (supportsVirtualTerminal != false)240 {241 switch (PSStyle.Instance.OutputRendering)242 {243 case OutputRendering.Host:244 outputRendering = isHost ? OutputRendering.Ansi : OutputRendering.PlainText;245 break;246 default:247 outputRendering = PSStyle.Instance.OutputRendering;248 break;249 }250 }251 252 return outputRendering == OutputRendering.PlainText;253 }254 255 /// <summary>256 /// The format styles that are supported by the host.257 /// </summary>258 public enum FormatStyle259 {260 /// <summary>261 /// Reset the formatting to the default.262 /// </summary>263 Reset,264 265 /// <summary>266 /// Highlight text used in output formatting.267 /// </summary>268 FormatAccent,269 270 /// <summary>271 /// Highlight for table headers.272 /// </summary>273 TableHeader,274 275 /// <summary>276 /// Highlight for detailed error view.277 /// </summary>278 ErrorAccent,279 280 /// <summary>281 /// Style for error messages.282 /// </summary>283 Error,284 285 /// <summary>286 /// Style for warning messages.287 /// </summary>288 Warning,289 290 /// <summary>291 /// Style for verbose messages.292 /// </summary>293 Verbose,294 295 /// <summary>296 /// Style for debug messages.297 /// </summary>298 Debug,299 }300 301 /// <summary>302 /// Get the ANSI escape sequence for the given format style.303 /// </summary>304 /// <param name="formatStyle">305 /// The format style to get the escape sequence for.306 /// </param>307 /// <returns>308 /// The ANSI escape sequence for the given format style.309 /// </returns>310 public static string GetFormatStyleString(FormatStyle formatStyle)311 {312 if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText)313 {314 return string.Empty;315 }316 317 PSStyle psstyle = PSStyle.Instance;318 switch (formatStyle)319 {320 case FormatStyle.Reset:321 return psstyle.Reset;322 case FormatStyle.FormatAccent:323 return psstyle.Formatting.FormatAccent;324 case FormatStyle.TableHeader:325 return psstyle.Formatting.TableHeader;326 case FormatStyle.ErrorAccent:327 return psstyle.Formatting.ErrorAccent;328 case FormatStyle.Error:329 return psstyle.Formatting.Error;330 case FormatStyle.Warning:331 return psstyle.Formatting.Warning;332 case FormatStyle.Verbose:333 return psstyle.Formatting.Verbose;334 case FormatStyle.Debug:335 return psstyle.Formatting.Debug;336 default:337 return string.Empty;338 }339 }340 341 /// <summary>342 /// Get the appropriate output string based on different criteria.343 /// </summary>344 /// <param name="text">345 /// The text to format.346 /// </param>347 /// <param name="supportsVirtualTerminal">348 /// True if the host supports virtual terminal.349 /// </param>350 /// <returns>351 /// The formatted text.352 /// </returns>353 public static string GetOutputString(string text, bool supportsVirtualTerminal)354 {355 return GetOutputString(text, isHost: true, supportsVirtualTerminal: supportsVirtualTerminal);356 }357 358 internal static string GetOutputString(string text, bool isHost, bool? supportsVirtualTerminal = null)359 {360 var sd = new ValueStringDecorated(text);361 362 if (sd.IsDecorated)363 {364 var outputRendering = OutputRendering.Ansi;365 if (ShouldOutputPlainText(isHost, supportsVirtualTerminal))366 {367 outputRendering = OutputRendering.PlainText;368 }369 370 text = sd.ToString(outputRendering);371 }372 373 return text;374 }375 376 // Gets the state associated with PowerShell transcription.377 //378 // Ideally, this would be associated with the host instance, but remoting recycles host instances379 // for each command that gets invoked (so that it can keep track of the order of commands and their380 // output.) Therefore, we store this transcription data in the runspace. However, the381 // Runspace.DefaultRunspace property isn't always available (i.e.: when the pipeline is being set up),382 // so we have to cache it the first time it becomes available.383 private TranscriptionData TranscriptionData384 {385 get386 {387 // If we have access to a runspace, use the transcription data for that runspace.388 // This is important when you have multiple runspaces within a host.389 LocalRunspace localRunspace = Runspace.DefaultRunspace as LocalRunspace;390 if (localRunspace != null)391 {392 _volatileTranscriptionData = localRunspace.TranscriptionData;393 if (_volatileTranscriptionData != null)394 {395 return _volatileTranscriptionData;396 }397 }398 399 // Otherwise, use the last stored transcription data. This will let us transcribe400 // errors where the runspace has gone away.401 if (_volatileTranscriptionData != null)402 {403 return _volatileTranscriptionData;404 }405 406 TranscriptionData temporaryTranscriptionData = new TranscriptionData();407 return temporaryTranscriptionData;408 }409 }410 411 private TranscriptionData _volatileTranscriptionData;412 413 /// <summary>414 /// Transcribes a command being invoked.415 /// </summary>416 /// <param name="commandText">The text of the command being invoked.</param>417 /// <param name="invocation">The invocation info of the command being transcribed.</param>418 internal void TranscribeCommand(string commandText, InvocationInfo invocation)419 {420 if (ShouldIgnoreCommand(commandText, invocation))421 {422 return;423 }424 425 if (IsTranscribing)426 {427 // We don't actually log the output here, because there may be multiple command invocations428 // in a single input - especially in the case of API logging, which logs the command and429 // its parameters as separate calls.430 // Instead, we add this to the 'pendingOutput' collection, which we flush when either431 // the command generates output, or when we are told to invoke ignore the next command.432 foreach (TranscriptionOption transcript in TranscriptionData.Transcripts.Prepend<TranscriptionOption>(TranscriptionData.SystemTranscript))433 {434 if (transcript != null)435 {436 lock (transcript.OutputToLog)437 {438 if (transcript.OutputToLog.Count == 0)439 {440 if (transcript.IncludeInvocationHeader)441 {442 transcript.OutputToLog.Add("**********************");443 transcript.OutputToLog.Add(444 string.Format(445 Globalization.CultureInfo.InvariantCulture, InternalHostUserInterfaceStrings.CommandStartTime,446 DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)));447 transcript.OutputToLog.Add("**********************");448 }449 450 transcript.OutputToLog.Add(TranscriptionData.PromptText + commandText);451 }452 else453 {454 transcript.OutputToLog.Add(">> " + commandText);455 }456 }457 }458 }459 }460 }461 462 private bool ShouldIgnoreCommand(string logElement, InvocationInfo invocation)463 {464 string commandName = logElement;465 466 if (invocation != null)467 {468 commandName = invocation.InvocationName;469 470 // Do not transcribe Out-Default471 CmdletInfo invocationCmdlet = invocation.MyCommand as CmdletInfo;472 if (invocationCmdlet != null)473 {474 if (invocationCmdlet.ImplementingType == typeof(Microsoft.PowerShell.Commands.OutDefaultCommand))475 {476 // We will ignore transcribing the command itself, but not call the IgnoreCommand() method477 // (because that will ignore the results)478 return true;479 }480 }481 482 // Don't log internal commands to the transcript.483 if (invocation.CommandOrigin == CommandOrigin.Internal)484 {485 IgnoreCommand(logElement, invocation);486 return true;487 }488 }489 490 // Don't log helper commands to the transcript491 string[] helperCommands = { "TabExpansion2", "prompt", "TabExpansion", "PSConsoleHostReadline" };492 foreach (string helperCommand in helperCommands)493 {494 if (string.Equals(helperCommand, commandName, StringComparison.OrdinalIgnoreCase))495 {496 IgnoreCommand(logElement, invocation);497 498 // Record that this is a helper command. In this case, we ignore even the results499 // from Out-Default500 TranscriptionData.IsHelperCommand = true;501 return true;502 }503 }504 505 return false;506 }507 508 /// <summary>509 /// Signals that a command being invoked (and its output) should be ignored.510 /// </summary>511 /// <param name="commandText">The text of the command being invoked.</param>512 /// <param name="invocation">The invocation info of the command being transcribed.</param>513 internal void IgnoreCommand(string commandText, InvocationInfo invocation)514 {515 TranscribeCommandComplete(null);516 517 if (TranscriptionData.CommandBeingIgnored == null)518 {519 TranscriptionData.CommandBeingIgnored = commandText;520 TranscriptionData.IsHelperCommand = false;521 522 if ((invocation != null) && (invocation.MyCommand != null))523 {524 TranscriptionData.CommandBeingIgnored = invocation.MyCommand.Name;525 }526 }527 }528 529 /// <summary>530 /// Flag to determine whether the host is in "Transcribe Only" mode,531 /// so that when content is sent through Out-Default it doesn't532 /// make it to the actual host.533 /// </summary>534 internal bool TranscribeOnly => Interlocked.CompareExchange(ref _transcribeOnlyCount, 0, 0) != 0;535 536 private int _transcribeOnlyCount = 0;537 538 internal IDisposable SetTranscribeOnly() => new TranscribeOnlyCookie(this);539 540 private sealed class TranscribeOnlyCookie : IDisposable541 {542 private readonly PSHostUserInterface _ui;543 private bool _disposed = false;544 545 public TranscribeOnlyCookie(PSHostUserInterface ui)546 {547 _ui = ui;548 Interlocked.Increment(ref _ui._transcribeOnlyCount);549 }550 551 public void Dispose()552 {553 if (!_disposed)554 {555 Interlocked.Decrement(ref _ui._transcribeOnlyCount);556 _disposed = true;557 GC.SuppressFinalize(this);558 }559 }560 561 ~TranscribeOnlyCookie() => Dispose();562 }563 564 /// <summary>565 /// Flag to determine whether the host is transcribing.566 /// </summary>567 internal bool IsTranscribing568 {569 get570 {571 CheckSystemTranscript();572 return (TranscriptionData.Transcripts.Count > 0) || (TranscriptionData.SystemTranscript != null);573 }574 }575 576 private void CheckSystemTranscript()577 {578 lock (TranscriptionData)579 {580 if (TranscriptionData.SystemTranscript == null)581 {582 TranscriptionData.SystemTranscript = GetSystemTranscriptOption(TranscriptionData.SystemTranscript);583 if (TranscriptionData.SystemTranscript != null)584 {585 LogTranscriptHeader(null, TranscriptionData.SystemTranscript);586 }587 }588 }589 }590 591 internal void StartTranscribing(string path, System.Management.Automation.Remoting.PSSenderInfo senderInfo, bool includeInvocationHeader, bool useMinimalHeader)592 {593 TranscriptionOption transcript = new TranscriptionOption();594 transcript.Path = path;595 transcript.IncludeInvocationHeader = includeInvocationHeader;596 TranscriptionData.Transcripts.Add(transcript);597 598 LogTranscriptHeader(senderInfo, transcript, useMinimalHeader);599 }600 601 private void LogTranscriptHeader(System.Management.Automation.Remoting.PSSenderInfo senderInfo, TranscriptionOption transcript, bool useMinimalHeader = false)602 {603 // Transcribe the transcript header604 string line;605 if (useMinimalHeader)606 {607 line =608 string.Format(609 Globalization.CultureInfo.InvariantCulture,610 InternalHostUserInterfaceStrings.MinimalTranscriptPrologue,611 DateTime.Now);612 }613 else614 {615 string username = Environment.UserDomainName + "\\" + Environment.UserName;616 string runAsUser = username;617 618 if (senderInfo != null)619 {620 username = senderInfo.UserInfo.Identity.Name;621 }622 623 // Add bits from PSVersionTable624 StringBuilder versionInfoFooter = new StringBuilder();625 Hashtable versionInfo = PSVersionInfo.GetPSVersionTable();626 foreach (string versionKey in versionInfo.Keys)627 {628 object value = versionInfo[versionKey];629 630 if (value != null)631 {632 var arrayValue = value as object[];633 string valueString = arrayValue != null ? string.Join(", ", arrayValue) : value.ToString();634 versionInfoFooter.AppendLine(versionKey + ": " + valueString);635 }636 }637 638 string configurationName = string.Empty;639 if (senderInfo != null && !string.IsNullOrEmpty(senderInfo.ConfigurationName))640 {641 configurationName = senderInfo.ConfigurationName;642 }643 644 line =645 string.Format(646 Globalization.CultureInfo.InvariantCulture,647 InternalHostUserInterfaceStrings.TranscriptPrologue,648 DateTime.Now,649 username,650 runAsUser,651 configurationName,652 Environment.MachineName,653 Environment.OSVersion.VersionString,654 string.Join(" ", Environment.GetCommandLineArgs()),655 Environment.ProcessId,656 versionInfoFooter.ToString().TrimEnd());657 }658 659 lock (transcript.OutputToLog)660 {661 transcript.OutputToLog.Add(line);662 }663 664 TranscribeCommandComplete(null);665 }666 667 internal string StopTranscribing()668 {669 if (TranscriptionData.Transcripts.Count == 0)670 {671 throw new PSInvalidOperationException(InternalHostUserInterfaceStrings.HostNotTranscribing);672 }673 674 TranscriptionOption stoppedTranscript = TranscriptionData.Transcripts[TranscriptionData.Transcripts.Count - 1];675 LogTranscriptFooter(stoppedTranscript);676 stoppedTranscript.Dispose();677 TranscriptionData.Transcripts.Remove(stoppedTranscript);678 679 return stoppedTranscript.Path;680 }681 682 private void LogTranscriptFooter(TranscriptionOption stoppedTranscript)683 {684 // Transcribe the transcript epilogue685 try686 {687 string message = string.Format(688 Globalization.CultureInfo.InvariantCulture,689 InternalHostUserInterfaceStrings.TranscriptEpilogue, DateTime.Now);690 691 lock (stoppedTranscript.OutputToLog)692 {693 stoppedTranscript.OutputToLog.Add(message);694 }695 696 TranscribeCommandComplete(null);697 }698 catch (Exception)699 {700 // Ignoring errors when stopping transcription (i.e.: file in use, access denied)701 // since this is probably handling exactly that error.702 }703 }704 705 internal void StopAllTranscribing()706 {707 TranscribeCommandComplete(null);708 709 while (TranscriptionData.Transcripts.Count > 0)710 {711 StopTranscribing();712 }713 714 lock (TranscriptionData)715 {716 if (TranscriptionData.SystemTranscript != null)717 {718 LogTranscriptFooter(TranscriptionData.SystemTranscript);719 TranscriptionData.SystemTranscript.Dispose();720 TranscriptionData.SystemTranscript = null;721 722 lock (s_systemTranscriptLock)723 {724 systemTranscript = null;725 }726 }727 }728 }729 730 /// <summary>731 /// Transcribes the supplied result text to the transcription buffer.732 /// </summary>733 /// <param name="sourceRunspace">The runspace that was used to generate this result, if it is not the current runspace.</param>734 /// <param name="resultText">The text to be transcribed.</param>735 internal void TranscribeResult(Runspace sourceRunspace, string resultText)736 {737 if (IsTranscribing)738 {739 // If the runspace that this result applies to is not the current runspace, update Runspace.DefaultRunspace740 // so that the transcript paths / etc. will be available to the TranscriptionData accessor.741 Runspace originalDefaultRunspace = null;742 if (sourceRunspace != null)743 {744 originalDefaultRunspace = Runspace.DefaultRunspace;745 Runspace.DefaultRunspace = sourceRunspace;746 }747 748 try749 {750 // If we're ignoring a command, ignore its output.751 if (TranscriptionData.CommandBeingIgnored != null)752 {753 // If we're ignoring a prompt, capture the value754 if (string.Equals("prompt", TranscriptionData.CommandBeingIgnored, StringComparison.OrdinalIgnoreCase))755 {756 TranscriptionData.PromptText = resultText;757 }758 759 return;760 }761 762 resultText = resultText.TrimEnd();763 764 var text = new ValueStringDecorated(resultText);765 if (text.IsDecorated)766 {767 resultText = text.ToString(OutputRendering.PlainText);768 }769 770 foreach (TranscriptionOption transcript in TranscriptionData.Transcripts.Prepend<TranscriptionOption>(TranscriptionData.SystemTranscript))771 {772 if (transcript != null)773 {774 lock (transcript.OutputToLog)775 {776 transcript.OutputToLog.Add(resultText);777 }778 }779 }780 }781 finally782 {783 if (originalDefaultRunspace != null)784 {785 Runspace.DefaultRunspace = originalDefaultRunspace;786 }787 }788 }789 }790 791 /// <summary>792 /// Transcribes the supplied result text to the transcription buffer.793 /// </summary>794 /// <param name="resultText">The text to be transcribed.</param>795 internal void TranscribeResult(string resultText)796 {797 TranscribeResult(null, resultText);798 }799 800 /// <summary>801 /// Transcribes / records the completion of a command.802 /// </summary>803 /// <param name="invocation"></param>804 internal void TranscribeCommandComplete(InvocationInfo invocation)805 {806 FlushPendingOutput();807 808 if (invocation != null)809 {810 // If we're ignoring a command that was internal, we still want the811 // results of Out-Default. However, if it was a host helper command,812 // ignore all output (including Out-Default)813 string commandNameToCheck = TranscriptionData.CommandBeingIgnored;814 if (TranscriptionData.IsHelperCommand)815 {816 commandNameToCheck = "Out-Default";817 }818 819 // If we're completing a command that we were ignoring, start transcribing results / etc. again.820 if ((TranscriptionData.CommandBeingIgnored != null) &&821 (invocation != null) && (invocation.MyCommand != null) &&822 string.Equals(commandNameToCheck, invocation.MyCommand.Name, StringComparison.OrdinalIgnoreCase))823 {824 TranscriptionData.CommandBeingIgnored = null;825 TranscriptionData.IsHelperCommand = false;826 }827 }828 }829 830 internal void TranscribePipelineComplete()831 {832 FlushPendingOutput();833 834 TranscriptionData.CommandBeingIgnored = null;835 TranscriptionData.IsHelperCommand = false;836 }837 838 private void FlushPendingOutput()839 {840 foreach (TranscriptionOption transcript in TranscriptionData.Transcripts.Prepend<TranscriptionOption>(TranscriptionData.SystemTranscript))841 {842 if (transcript != null)843 {844 lock (transcript.OutputToLog)845 {846 if (transcript.OutputToLog.Count == 0)847 {848 continue;849 }850 851 lock (transcript.OutputBeingLogged)852 {853 bool alreadyLogging = transcript.OutputBeingLogged.Count > 0;854 855 transcript.OutputBeingLogged.AddRange(transcript.OutputToLog);856 transcript.OutputToLog.Clear();857 858 // If there is already a thread trying to log output, add this output to its buffer859 // and don't start a new thread.860 if (alreadyLogging)861 {862 continue;863 }864 }865 }866 867 // Create the file in the main thread and flush the contents in the background thread.868 // Transcription should begin only if file generation is successful.869 // If there is an error in file generation, throw the exception.870 string baseDirectory = Path.GetDirectoryName(transcript.Path);871 if (Directory.Exists(transcript.Path) || (string.Equals(baseDirectory, transcript.Path.TrimEnd(Path.DirectorySeparatorChar), StringComparison.Ordinal)))872 {873 string errorMessage = string.Format(874 System.Globalization.CultureInfo.CurrentCulture,875 InternalHostUserInterfaceStrings.InvalidTranscriptFilePath,876 transcript.Path);877 throw new ArgumentException(errorMessage);878 }879 880 if (!Directory.Exists(baseDirectory))881 {882 Directory.CreateDirectory(baseDirectory);883 }884 885 if (!File.Exists(transcript.Path))886 {887 File.Create(transcript.Path).Dispose();888 }889 890 // Do the actual writing in the background so that it doesn't hold up the UI thread.891 Task writer = Task.Run(() =>892 {893 // System transcripts can have high contention. Do exponential back-off on writing894 // if needed.895 int delay = Random.Shared.Next(10) + 1;896 bool written = false;897 898 while (!written)899 {900 try901 {902 transcript.FlushContentToDisk();903 written = true;904 }905 catch (IOException)906 {907 System.Threading.Thread.Sleep(delay);908 }909 catch (UnauthorizedAccessException)910 {911 System.Threading.Thread.Sleep(delay);912 }913 914 // If we are trying to log, but weren't able too, back of the sleep.915 // If we're already sleeping for 1 second between tries, then just continue916 // at this pace until the write is successful.917 if (delay < 1000)918 {919 delay *= 2;920 }921 }922 });923 }924 }925 }926 927 #endregion Line-oriented interaction928 929 #region Dialog-oriented Interaction930 931 /// <summary>932 /// Constructs a 'dialog' where the user is presented with a number of fields for which to supply values.933 /// </summary>934 /// <param name="caption">935 /// Caption to precede or title the prompt. E.g. "Parameters for get-foo (instance 1 of 2)"936 /// </param>937 /// <param name="message">938 /// A text description of the set of fields to be prompt.939 /// </param>940 /// <param name="descriptions">941 /// Array of FieldDescriptions that contain information about each field to be prompted for.942 /// </param>943 /// <returns>944 /// A Dictionary object with results of prompting. The keys are the field names from the FieldDescriptions, the values945 /// are objects representing the values of the corresponding fields as collected from the user. To the extent possible,946 /// the host should return values of the type(s) identified in the FieldDescription. When that is not possible (for947 /// example, the type is not available to the host), the host should return the value as a string.948 /// </returns>949 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLine"/>950 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLineAsSecureString"/>951 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForChoice"/>952 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string)"/>953 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string, System.Management.Automation.PSCredentialTypes, System.Management.Automation.PSCredentialUIOptions)"/>954 public abstract Dictionary<string, PSObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions);955 956 /// <summary>957 /// Prompt for credentials.958 /// <!--In future, when we have Credential object from the security team,959 /// this function will be modified to prompt using secure-path960 /// if so configured.-->961 /// </summary>962 /// <summary>963 /// Prompt for credential.964 /// </summary>965 /// <param name="caption">966 /// Caption for the message.967 /// </param>968 /// <param name="message">969 /// Text description for the credential to be prompt.970 /// </param>971 /// <param name="userName">972 /// Name of the user whose credential is to be prompted for. If set to null or empty973 /// string, the function will prompt for user name first.974 /// </param>975 /// <param name="targetName">976 /// Name of the target for which the credential is being collected.977 /// </param>978 /// <returns>979 /// User input credential.980 /// </returns>981 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLine"/>982 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLineAsSecureString"/>983 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Prompt"/>984 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForChoice"/>985 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string, System.Management.Automation.PSCredentialTypes, System.Management.Automation.PSCredentialUIOptions)"/>986 public abstract PSCredential PromptForCredential(string caption, string message,987 string userName, string targetName988 );989 990 /// <summary>991 /// Prompt for credential.992 /// </summary>993 /// <param name="caption">994 /// Caption for the message.995 /// </param>996 /// <param name="message">997 /// Text description for the credential to be prompt.998 /// </param>999 /// <param name="userName">1000 /// Name of the user whose credential is to be prompted for. If set to null or empty1001 /// string, the function will prompt for user name first.1002 /// </param>1003 /// <param name="targetName">1004 /// Name of the target for which the credential is being collected.1005 /// </param>1006 /// <param name="allowedCredentialTypes">1007 /// Types of credential can be supplied by the user.1008 /// </param>1009 /// <param name="options">1010 /// Options that control the credential gathering UI behavior1011 /// </param>1012 /// <returns>1013 /// User input credential.1014 /// </returns>1015 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLine"/>1016 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLineAsSecureString"/>1017 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Prompt"/>1018 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForChoice"/>1019 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string)"/>1020 public abstract PSCredential PromptForCredential(string caption, string message,1021 string userName, string targetName, PSCredentialTypes allowedCredentialTypes,1022 PSCredentialUIOptions options1023 );1024 1025 /// <summary>1026 /// Presents a dialog allowing the user to choose an option from a set of options.1027 /// </summary>1028 /// <param name="caption">1029 /// Caption to precede or title the prompt. E.g. "Parameters for get-foo (instance 1 of 2)"1030 /// </param>1031 /// <param name="message">1032 /// A message that describes what the choice is for.1033 /// </param>1034 /// <param name="choices">1035 /// An Collection of ChoiceDescription objects that describe each choice.1036 /// </param>1037 /// <param name="defaultChoice">1038 /// The index of the label in the choices collection element to be presented to the user as the default choice. -11039 /// means "no default". Must be a valid index.1040 /// </param>1041 /// <returns>1042 /// The index of the choices element that corresponds to the option selected.1043 /// </returns>1044 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLine"/>1045 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLineAsSecureString"/>1046 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Prompt"/>1047 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string)"/>1048 /// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string, System.Management.Automation.PSCredentialTypes, System.Management.Automation.PSCredentialUIOptions)"/>1049 public abstract int PromptForChoice(string caption, string message, Collection<ChoiceDescription> choices, int defaultChoice);1050 1051 #endregion Dialog-oriented interaction1052 1053 /// <summary>1054 /// Creates a new instance of the PSHostUserInterface class.1055 /// </summary>1056 protected PSHostUserInterface()1057 {1058 CheckSystemTranscript();1059 }1060 1061 /// <summary>1062 /// Helper to transcribe an error through formatting and output.1063 /// </summary>1064 /// <param name="context">The Execution Context.</param>1065 /// <param name="invocation">The invocation info associated with the record.</param>1066 /// <param name="errorWrap">The error record.</param>1067 internal void TranscribeError(ExecutionContext context, InvocationInfo invocation, PSObject errorWrap)1068 {1069 context.InternalHost.UI.TranscribeCommandComplete(invocation);1070 InitialSessionState minimalState = InitialSessionState.CreateDefault2();1071 Collection<PSObject> results = PowerShell.Create(minimalState).AddCommand("Out-String").Invoke(1072 new List<PSObject>() { errorWrap });1073 TranscribeResult(results[0].ToString());1074 }1075 1076 /// <summary>1077 /// Get Module Logging information from the registry.1078 /// </summary>1079 internal static TranscriptionOption GetSystemTranscriptOption(TranscriptionOption currentTranscript)1080 {1081 var transcription = InternalTestHooks.BypassGroupPolicyCaching1082 ? Utils.GetPolicySetting<Transcription>(Utils.SystemWideThenCurrentUserConfig)1083 : s_transcriptionSettingCache.Value;1084 1085 if (transcription != null)1086 {1087 // If we have an existing system transcript for this process, use that.1088 // Otherwise, populate the static variable with the result of the group policy setting.1089 //1090 // This way, multiple runspaces opened by the same process will share the same transcript.1091 lock (s_systemTranscriptLock)1092 {1093 systemTranscript ??= PSHostUserInterface.GetTranscriptOptionFromSettings(transcription, currentTranscript);1094 }1095 }1096 1097 return systemTranscript;1098 }1099 1100 internal static TranscriptionOption systemTranscript = null;1101 private static readonly object s_systemTranscriptLock = new object();1102 1103 private static readonly Lazy<Transcription> s_transcriptionSettingCache = new Lazy<Transcription>(1104 static () => Utils.GetPolicySetting<Transcription>(Utils.SystemWideThenCurrentUserConfig),1105 isThreadSafe: true);1106 1107 private static TranscriptionOption GetTranscriptOptionFromSettings(Transcription transcriptConfig, TranscriptionOption currentTranscript)1108 {1109 TranscriptionOption transcript = null;1110 1111 if (transcriptConfig.EnableTranscripting == true)1112 {1113 if (currentTranscript != null)1114 {1115 return currentTranscript;1116 }1117 1118 transcript = new TranscriptionOption();1119 1120 // Pull out the transcript path1121 if (transcriptConfig.OutputDirectory != null)1122 {1123 transcript.Path = GetTranscriptPath(transcriptConfig.OutputDirectory, true);1124 }1125 else1126 {1127 transcript.Path = GetTranscriptPath();1128 }1129 1130 // Pull out the "enable invocation header"1131 transcript.IncludeInvocationHeader = transcriptConfig.EnableInvocationHeader == true;1132 }1133 1134 return transcript;1135 }1136 1137 internal static string GetTranscriptPath()1138 {1139 string baseDirectory = Platform.GetFolderPath(Environment.SpecialFolder.MyDocuments);1140 return GetTranscriptPath(baseDirectory, false);1141 }1142 1143 internal static string GetTranscriptPath(string baseDirectory, bool includeDate)1144 {1145 if (string.IsNullOrEmpty(baseDirectory))1146 {1147 baseDirectory = Platform.GetFolderPath(Environment.SpecialFolder.MyDocuments);1148 }1149 else1150 {1151 if (!Path.IsPathRooted(baseDirectory))1152 {1153 baseDirectory = Path.Combine(1154 Platform.GetFolderPath(Environment.SpecialFolder.MyDocuments),1155 baseDirectory);1156 }1157 }1158 1159 if (string.IsNullOrEmpty(baseDirectory))1160 {1161 return string.Empty;1162 }1163 1164 if (includeDate)1165 {1166 baseDirectory = Path.Combine(baseDirectory, DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture));1167 }1168 1169 // transcriptPath includes some randomness so that files can be collected on a central share,1170 // and an attacker can't guess the filename and read the contents if the ACL was poor.1171 // After testing, a computer can do about 10,000 remote path tests per second. So 61172 // bytes of randomness (2^48 = 2.8e14) would take an attacker about 891 years to guess1173 // a filename (assuming they knew the time the transcript was started).1174 // (5 bytes = 3 years, 4 bytes = about a month)1175 Span<byte> randomBytes = stackalloc byte[6];1176 System.Security.Cryptography.RandomNumberGenerator.Fill(randomBytes);1177 string filename = string.Format(1178 Globalization.CultureInfo.InvariantCulture,1179 "PowerShell_transcript.{0}.{1}.{2:yyyyMMddHHmmss}.txt",1180 Environment.MachineName,1181 Convert.ToBase64String(randomBytes).Replace('/', '_'),1182 DateTime.Now);1183 1184 string transcriptPath = System.IO.Path.Combine(baseDirectory, filename);1185 return transcriptPath;1186 }1187 }1188 1189 // Holds runspace-wide transcription data / settings for PowerShell transcription1190 internal class TranscriptionData1191 {1192 internal TranscriptionData()1193 {1194 Transcripts = new List<TranscriptionOption>();1195 SystemTranscript = null;1196 CommandBeingIgnored = null;1197 IsHelperCommand = false;1198 PromptText = "PS>";1199 }1200 