MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Globalization;6using System.Management.Automation.Host;7using System.Management.Automation.Remoting;8using System.Management.Automation.Runspaces;9 10using Dbg = System.Management.Automation.Diagnostics;11 12#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings13 14namespace System.Management.Automation.Internal.Host15{16 /// <summary>17 /// Wraps PSHost instances to provide a shim layer18 /// between InternalCommand and the host-supplied PSHost instance.19 ///20 /// This class exists for the purpose of ensuring that an externally-supplied PSHost meets the minimum proper required21 /// implementation, and also to provide a leverage point at which the monad engine can hook the interaction between the engine,22 /// cmdlets, and that external host.23 ///24 /// That leverage may be necessary to manage concurrent access between multiple pipelines sharing the same instance of25 /// PSHost.26 /// </summary>27 internal class InternalHost : PSHost, IHostSupportsInteractiveSession28 {29 /// <summary>30 /// There should only be one instance of InternalHost per runspace (i.e. per engine), and all engine use of the host31 /// should be through that single instance. If we ever accidentally create more than one instance of InternalHost per32 /// runspace, then some of the internal state checks that InternalHost makes, like checking the nestedPromptCounter, can33 /// be messed up.34 ///35 /// To ensure that this constraint is met, I wanted to make this class a singleton. However, Hitesh rightly pointed out36 /// that a singleton would be appdomain-global, which would prevent having multiple runspaces per appdomain. So we will37 /// just have to be careful not to create extra instances of InternalHost per runspace.38 /// </summary>39 internal InternalHost(PSHost externalHost, ExecutionContext executionContext)40 {41 Dbg.Assert(externalHost != null, "must supply an PSHost");42 Dbg.Assert(externalHost is not InternalHost, "try to create an InternalHost from another InternalHost");43 44 Dbg.Assert(executionContext != null, "must supply an ExecutionContext");45 46 _externalHostRef = new ObjectRef<PSHost>(externalHost);47 Context = executionContext;48 49 PSHostUserInterface ui = externalHost.UI;50 51 _internalUIRef = new ObjectRef<InternalHostUserInterface>(new InternalHostUserInterface(ui, this));52 _zeroGuid = new Guid(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);53 _idResult = _zeroGuid;54 }55 56 /// <summary>57 /// See base class.58 /// </summary>59 /// <value></value>60 /// <exception cref="NotImplementedException">61 /// when the external host's Name is null or empty.62 /// </exception>63 public override string Name64 {65 get66 {67 if (string.IsNullOrEmpty(_nameResult))68 {69 _nameResult = _externalHostRef.Value.Name;70 71#pragma warning disable 5650372 if (string.IsNullOrEmpty(_nameResult))73 {74 throw PSTraceSource.NewNotImplementedException();75 }76#pragma warning restore 5650377 }78 79 return _nameResult;80 }81 }82 83 /// <summary>84 /// See base class.85 /// </summary>86 /// <value></value>87 /// <exception cref="NotImplementedException">88 /// when the external host's Version is null.89 /// </exception>90 public override System.Version Version91 {92 get93 {94 if (_versionResult == null)95 {96 _versionResult = _externalHostRef.Value.Version;97 98#pragma warning disable 5650399 if (_versionResult == null)100 {101 throw PSTraceSource.NewNotImplementedException();102 }103#pragma warning restore 56503104 }105 106 return _versionResult;107 }108 }109 110 /// <summary>111 /// See base class.112 /// </summary>113 /// <value></value>114 /// <exception cref="NotImplementedException">115 /// when the external host's InstanceId is a zero Guid.116 /// </exception>117 public override System.Guid InstanceId118 {119 get120 {121 if (_idResult == _zeroGuid)122 {123 _idResult = _externalHostRef.Value.InstanceId;124 125#pragma warning disable 56503126 if (_idResult == _zeroGuid)127 {128 throw PSTraceSource.NewNotImplementedException();129 }130#pragma warning restore 56503131 }132 133 return _idResult;134 }135 }136 137 /// <summary>138 /// See base class.139 /// </summary>140 /// <value>141 /// </value>142 public override System.Management.Automation.Host.PSHostUserInterface UI143 {144 get145 {146 return _internalUIRef.Value;147 }148 }149 150 /// <summary>151 /// Interface to be used for interaction with internal152 /// host UI. InternalHostUserInterface wraps the host UI153 /// supplied during construction. Use this wrapper to access154 /// functionality specific to InternalHost.155 /// </summary>156 internal InternalHostUserInterface InternalUI157 {158 get159 {160 return _internalUIRef.Value;161 }162 }163 164 /// <summary>165 /// See base class.166 /// </summary>167 /// <value>168 /// </value>169 /// <exception cref="NotImplementedException">170 /// when the external host's CurrentCulture is null.171 /// </exception>172 public override System.Globalization.CultureInfo CurrentCulture173 {174 get175 {176 CultureInfo ci = _externalHostRef.Value.CurrentCulture ?? CultureInfo.InvariantCulture;177 178 return ci;179 }180 }181 182 /// <summary>183 /// See base class.184 /// </summary>185 /// <value>186 /// </value>187 /// <exception cref="NotImplementedException">188 /// If the external host's CurrentUICulture is null.189 /// </exception>190 public override CultureInfo CurrentUICulture191 {192 get193 {194 CultureInfo ci = _externalHostRef.Value.CurrentUICulture ?? CultureInfo.InstalledUICulture;195 return ci;196 }197 }198 199 /// <summary>200 /// See base class.201 /// </summary>202 /// <param name="exitCode"></param>203 public override void SetShouldExit(int exitCode)204 {205 _externalHostRef.Value.SetShouldExit(exitCode);206 }207 208 /// <summary>209 /// See base class210 /// <seealso cref="ExitNestedPrompt"/>211 /// </summary>212 public override void EnterNestedPrompt()213 {214 EnterNestedPrompt(null);215 }216 217 private struct PromptContextData218 {219 public object SavedCurrentlyExecutingCommandVarValue;220 public object SavedPSBoundParametersVarValue;221 public ExecutionContext.SavedContextData SavedContextData;222 public RunspaceAvailability RunspaceAvailability;223 public PSLanguageMode LanguageMode;224 }225 226 /// <summary>227 /// Internal proxy for EnterNestedPrompt.228 /// </summary>229 /// <param name="callingCommand"></param>230 internal void EnterNestedPrompt(InternalCommand callingCommand)231 {232 // Ensure we are in control of the pipeline233 LocalRunspace localRunspace = null;234 235 // This needs to be in a try / catch, since the LocalRunspace cast236 // tries to verify that the host supports interactive sessions.237 // Tests hosts do not.238 try { localRunspace = this.Runspace as LocalRunspace; }239 catch (PSNotImplementedException) { }240 241 if (localRunspace != null)242 {243 Pipeline currentlyRunningPipeline = this.Runspace.GetCurrentlyRunningPipeline();244 245 if ((currentlyRunningPipeline != null) &&246 (currentlyRunningPipeline == localRunspace.PulsePipeline))247 throw new InvalidOperationException();248 }249 250 // NTRAID#Windows OS Bugs-986407-2004/07/29 When kumarp has done the configuration work in the engine, it251 // should include setting a bit that indicates that the initialization is complete, and code should be252 // added here to throw an exception if this function is called before that bit is set.253 254 if (NestedPromptCount < 0)255 {256 Dbg.Assert(false, "nested prompt counter should never be negative.");257 throw PSTraceSource.NewInvalidOperationException(258 InternalHostStrings.EnterExitNestedPromptOutOfSync);259 }260 261 // Increment our nesting counter. When we set the value of the variable, we will replace any existing variable262 // of the same name. This is good, as any existing value is either 1) ours, and we have claim to replace it, or263 // 2) is a squatter, and we have claim to clobber it.264 265 ++NestedPromptCount;266 Context.SetVariable(SpecialVariables.NestedPromptCounterVarPath, NestedPromptCount);267 268 // On entering a subshell, save and reset values of certain bits of session state269 270 PromptContextData contextData = new PromptContextData();271 contextData.SavedContextData = Context.SaveContextData();272 contextData.SavedCurrentlyExecutingCommandVarValue = Context.GetVariableValue(SpecialVariables.CurrentlyExecutingCommandVarPath);273 contextData.SavedPSBoundParametersVarValue = Context.GetVariableValue(SpecialVariables.PSBoundParametersVarPath);274 contextData.RunspaceAvailability = this.Context.CurrentRunspace.RunspaceAvailability;275 contextData.LanguageMode = Context.LanguageMode;276 277 PSPropertyInfo commandInfoProperty = null;278 PSPropertyInfo stackTraceProperty = null;279 object oldCommandInfo = null;280 object oldStackTrace = null;281 if (callingCommand != null)282 {283 Dbg.Assert(callingCommand.Context == Context, "I expect that the contexts should match");284 285 // Populate $CurrentlyExecutingCommand to facilitate debugging. One of the gotchas is that we are going to want286 // to expose more and more debug info. We could just populate more and more local variables but that is probably287 // a lousy approach as it pollutes the namespace. A better way to do it is to add NOTES to the variable value288 // object.289 290 PSObject newValue = PSObject.AsPSObject(callingCommand);291 292 commandInfoProperty = newValue.Properties["CommandInfo"];293 if (commandInfoProperty == null)294 {295 newValue.Properties.Add(new PSNoteProperty("CommandInfo", callingCommand.CommandInfo));296 }297 else298 {299 oldCommandInfo = commandInfoProperty.Value;300 commandInfoProperty.Value = callingCommand.CommandInfo;301 }302 303 stackTraceProperty = newValue.Properties["StackTrace"];304 if (stackTraceProperty == null)305 {306 newValue.Properties.Add(new PSNoteProperty("StackTrace", new System.Diagnostics.StackTrace()));307 }308 else309 {310 oldStackTrace = stackTraceProperty.Value;311 stackTraceProperty.Value = new System.Diagnostics.StackTrace();312 }313 314 Context.SetVariable(SpecialVariables.CurrentlyExecutingCommandVarPath, newValue);315 }316 317 _contextStack.Push(contextData);318 Dbg.Assert(_contextStack.Count == NestedPromptCount, "number of saved contexts should equal nesting count");319 320 Context.PSDebugTraceStep = false;321 Context.PSDebugTraceLevel = 0;322 Context.ResetShellFunctionErrorOutputPipe();323 324 // Lock down the language in the nested prompt325 if (Context.HasRunspaceEverUsedConstrainedLanguageMode)326 {327 Context.LanguageMode = PSLanguageMode.ConstrainedLanguage;328 }329 330 this.Context.CurrentRunspace.UpdateRunspaceAvailability(RunspaceAvailability.AvailableForNestedCommand, true);331 332 try333 {334 _externalHostRef.Value.EnterNestedPrompt();335 }336 catch337 {338 // So where things really go south is this path; which is possible for hosts (like our ConsoleHost)339 // that don't return from EnterNestedPrompt immediately.340 // EnterNestedPrompt() starts341 // ExitNestedPrompt() called342 // EnterNestedPrompt throws343 344 ExitNestedPromptHelper();345 throw;346 }347 finally348 {349 if (commandInfoProperty != null)350 {351 commandInfoProperty.Value = oldCommandInfo;352 }353 354 if (stackTraceProperty != null)355 {356 stackTraceProperty.Value = oldStackTrace;357 }358 }359 360 Dbg.Assert(NestedPromptCount >= 0, "nestedPromptCounter should be greater than or equal to 0");361 }362 363 private void ExitNestedPromptHelper()364 {365 --NestedPromptCount;366 Context.SetVariable(SpecialVariables.NestedPromptCounterVarPath, NestedPromptCount);367 368 // restore the saved context369 370 Dbg.Assert(_contextStack.Count > 0, "ExitNestedPrompt: called without any saved context");371 372 if (_contextStack.Count > 0)373 {374 PromptContextData pcd = _contextStack.Pop();375 376 pcd.SavedContextData.RestoreContextData(Context);377 Context.LanguageMode = pcd.LanguageMode;378 Context.SetVariable(SpecialVariables.CurrentlyExecutingCommandVarPath, pcd.SavedCurrentlyExecutingCommandVarValue);379 Context.SetVariable(SpecialVariables.PSBoundParametersVarPath, pcd.SavedPSBoundParametersVarValue);380 this.Context.CurrentRunspace.UpdateRunspaceAvailability(pcd.RunspaceAvailability, true);381 }382 383 Dbg.Assert(_contextStack.Count == NestedPromptCount, "number of saved contexts should equal nesting count");384 }385 386 /// <summary>387 /// See base class388 /// <seealso cref="EnterNestedPrompt()"/>389 /// </summary>390 public override void ExitNestedPrompt()391 {392 Dbg.Assert(NestedPromptCount >= 0, "nestedPromptCounter should be greater than or equal to 0");393 394 if (NestedPromptCount == 0)395 return;396 397 try398 {399 _externalHostRef.Value.ExitNestedPrompt();400 }401 finally402 {403 ExitNestedPromptHelper();404 }405 406 ExitNestedPromptException enpe = new ExitNestedPromptException();407 throw enpe;408 }409 410 /// <summary>411 /// See base class.412 /// </summary>413 public override PSObject PrivateData414 {415 get416 {417 PSObject result = _externalHostRef.Value.PrivateData;418 return result;419 }420 }421 422 /// <summary>423 /// See base class424 /// <seealso cref="NotifyEndApplication"/>425 /// </summary>426 public override void NotifyBeginApplication()427 {428 _externalHostRef.Value.NotifyBeginApplication();429 }430 431 /// <summary>432 /// Called by the engine to notify the host that the execution of a legacy command has completed.433 /// <seealso cref="NotifyBeginApplication"/>434 /// </summary>435 public override void NotifyEndApplication()436 {437 _externalHostRef.Value.NotifyEndApplication();438 }439 440 /// <summary>441 /// This property enables and disables the host debugger if debugging is supported.442 /// </summary>443 public override bool DebuggerEnabled { get; set; } = true;444 445 /// <summary>446 /// Gets the external host as an IHostSupportsInteractiveSession if it implements this interface;447 /// throws an exception otherwise.448 /// </summary>449 private IHostSupportsInteractiveSession GetIHostSupportsInteractiveSession()450 {451 if (_externalHostRef.Value is not IHostSupportsInteractiveSession host)452 {453 throw new PSNotImplementedException();454 }455 456 return host;457 }458 459 /// <summary>460 /// Called by the engine to notify the host that a runspace push has been requested.461 /// </summary>462 /// <seealso cref="PopRunspace"/>463 public void PushRunspace(System.Management.Automation.Runspaces.Runspace runspace)464 {465 IHostSupportsInteractiveSession host = GetIHostSupportsInteractiveSession();466 host.PushRunspace(runspace);467 }468 469 /// <summary>470 /// Called by the engine to notify the host that a runspace pop has been requested.471 /// </summary>472 /// <seealso cref="PushRunspace"/>473 public void PopRunspace()474 {475 IHostSupportsInteractiveSession host = GetIHostSupportsInteractiveSession();476 host.PopRunspace();477 }478 479 /// <summary>480 /// True if a runspace is pushed; false otherwise.481 /// </summary>482 public bool IsRunspacePushed483 {484 get485 {486 IHostSupportsInteractiveSession host = GetIHostSupportsInteractiveSession();487 return host.IsRunspacePushed;488 }489 }490 491 /// <summary>492 /// Returns the current runspace associated with this host.493 /// </summary>494 public Runspace Runspace495 {496 get497 {498 IHostSupportsInteractiveSession host = GetIHostSupportsInteractiveSession();499 return host.Runspace;500 }501 }502 503 /// <summary>504 /// Checks if the host is in a nested prompt.505 /// </summary>506 /// <returns>True, if host in nested prompt507 /// false, otherwise.</returns>508 internal bool HostInNestedPrompt()509 {510 if (NestedPromptCount > 0)511 {512 return true;513 }514 else515 {516 return false;517 }518 }519 520 /// <summary>521 /// Sets the reference to the external host and the internal UI to a temporary522 /// new host and its UI. This exists so that if the PowerShell/Pipeline523 /// object has a different host from the runspace it can set it's host during its524 /// invocation, and then revert it after the invocation is completed.525 /// </summary>526 /// <seealso cref="RevertHostRef"/> and527 internal void SetHostRef(PSHost psHost)528 {529 _externalHostRef.Override(psHost);530 _internalUIRef.Override(new InternalHostUserInterface(psHost.UI, this));531 }532 533 /// <summary>534 /// Reverts the temporary host set by SetHost. If no host was temporarily set, this has no effect.535 /// </summary>536 /// <seealso cref="SetHostRef"/> and537 internal void RevertHostRef()538 {539 // nothing to revert if Host reference is not set.540 if (!IsHostRefSet)541 {542 return;543 }544 545 _externalHostRef.Revert();546 _internalUIRef.Revert();547 }548 549 /// <summary>550 /// Returns true if the external host reference is temporarily set to another host, masking the original host.551 /// </summary>552 internal bool IsHostRefSet553 {554 get { return _externalHostRef.IsOverridden; }555 }556 557 internal ExecutionContext Context { get; }558 559 internal PSHost ExternalHost560 {561 get562 {563 return _externalHostRef.Value;564 }565 }566 567 internal int NestedPromptCount { get; private set; }568 569 // Masked variables.570 private readonly ObjectRef<PSHost> _externalHostRef;571 private readonly ObjectRef<InternalHostUserInterface> _internalUIRef;572 573 // Private variables.574 private string _nameResult;575 private Version _versionResult;576 private Guid _idResult;577 private readonly Stack<PromptContextData> _contextStack = new Stack<PromptContextData>();578 579 private readonly Guid _zeroGuid;580 }581}582 