MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.Management.Automation.Runspaces;7 8namespace System.Management.Automation.Internal9{10 /// <summary>11 /// Corresponds to -OutputVariable, -ErrorVariable, -WarningVariable, and -InformationVariable.12 /// </summary>13 internal enum VariableStreamKind14 {15 Output,16 Error,17 Warning,18 Information19 }20 21 /// <summary>22 /// Pipe provides a way to stitch two commands.23 /// </summary>24 /// <remarks>25 /// The Pipe class is not thread-safe, so methods such as26 /// AddItems and Retrieve should not be called simultaneously.27 /// ExternalReader and ExternalWriter can provide thread-safe buffering.28 /// </remarks>29 internal class Pipe30 {31 private readonly ExecutionContext _context;32 33 // If a pipeline object has been added, then34 // write objects to it, stepping one at a time...35 internal PipelineProcessor PipelineProcessor { get; }36 37 /// <summary>38 /// This is the downstream cmdlet in the "streamlet model"39 /// which is invoked during each call to Add/AddItems.40 /// </summary>41 internal CommandProcessorBase DownstreamCmdlet42 {43 get44 {45 return _downstreamCmdlet;46 }47 48 set49 {50 Diagnostics.Assert(_resultList == null, "Tried to set downstream cmdlet when _resultList not null");51 _downstreamCmdlet = value;52 }53 }54 55 private CommandProcessorBase _downstreamCmdlet;56 57 /// <summary>58 /// This is the upstream external object source. If this is set,59 /// Retrieve() will attempt to read objects from the upstream source60 /// before indicating that the pipe is empty.61 /// <remarks>62 /// It is improper to change this once the pipeline has started63 /// executing, although the checks for this are in the64 /// PipelineProcessor class and not here.65 /// </remarks>66 /// </summary>67 internal PipelineReader<object> ExternalReader { get; set; }68 69 /// <summary>70 /// This is the downstream object recipient. If this is set,71 /// Add() and AddItems() write to this recipient instead of72 /// to the internal queue. This also disables the73 /// DownstreamCmdlet.74 /// <remarks>75 /// It is improper to change this once the pipeline has started76 /// executing, although the checks for this are in the77 /// PipelineProcessor class and not here.78 /// </remarks>79 /// </summary>80 internal PipelineWriter ExternalWriter81 {82 get83 {84 return _externalWriter;85 }86 87 set88 {89 Diagnostics.Assert(_resultList == null, "Tried to set Pipe ExternalWriter when resultList not null");90 _externalWriter = value;91 }92 }93 94 private PipelineWriter _externalWriter;95 96 /// <summary>97 /// For diagnostic purposes.98 /// </summary>99 /// <returns></returns>100 public override string ToString()101 {102 if (_downstreamCmdlet != null)103 return _downstreamCmdlet.ToString();104 return base.ToString();105 }106 107 /// <summary>108 /// OutBufferCount configures the number of objects to buffer before calling the downstream Cmdlet.109 /// </summary>110 internal int OutBufferCount { get; set; } = 0;111 112 /// <summary>113 /// Gets whether the out variable list should be ignored.114 /// This is used for scenarios like the `clean` block, where writing to output stream is intentionally115 /// disabled and thus out variables should also be ignored.116 /// </summary>117 internal bool IgnoreOutVariableList { get; set; }118 119 /// <summary>120 /// If true, then all input added to this pipe will simply be discarded...121 /// </summary>122 internal bool NullPipe123 {124 get125 {126 return _nullPipe;127 }128 129 set130 {131 _isRedirected = true;132 _nullPipe = value;133 }134 }135 136 private bool _nullPipe;137 138 /// <summary>139 /// A queue that is shared between commands on either side of the pipe to transfer objects.140 /// </summary>141 internal Queue<object> ObjectQueue { get; }142 143 /// <summary>144 /// True if there are items in this pipe that need processing...145 /// <remarks>146 /// This does not take into account the presence of ExternalInput;147 /// it only indicates whether there is currently any data queued up148 /// or if there is data in the enumerator...149 /// </remarks>150 /// </summary>151 internal bool Empty152 {153 get154 {155 if (_enumeratorToProcess != null)156 return _enumeratorToProcessIsEmpty;157 158 if (ObjectQueue != null)159 return ObjectQueue.Count == 0;160 return true;161 }162 }163 164 /// <summary>165 /// Is true if there is someone consuming this pipe already, either through166 /// a Pipe object that processes it's output or there is downstream cmdlet...167 /// </summary>168 internal bool IsRedirected169 {170 get { return _downstreamCmdlet != null || _isRedirected; }171 }172 173 private bool _isRedirected;174 175 /// <summary>176 /// If non-null, output written to the pipe are also added to this list.177 /// </summary>178 private List<IList> _outVariableList;179 180 /// <summary>181 /// If non-null, errors written to the pipe are also added to this list.182 /// </summary>183 private List<IList> _errorVariableList;184 185 /// <summary>186 /// If non-null, warnings written to the pipe are also added to this list.187 /// </summary>188 private List<IList> _warningVariableList;189 190 /// <summary>191 /// If non-null, information objects written to the pipe are also added to this list.192 /// </summary>193 private List<IList> _informationVariableList;194 195 /// <summary>196 /// If non-null, the current object being written to the pipe is stored in197 /// this variable.198 /// </summary>199 private PSVariable _pipelineVariableObject;200 201 private static void AddToVarList(List<IList> varList, object obj)202 {203 if (varList != null && varList.Count > 0)204 {205 for (int i = 0; i < varList.Count; i++)206 {207 varList[i].Add(obj);208 }209 }210 }211 212 internal void AppendVariableList(VariableStreamKind kind, object obj)213 {214 switch (kind)215 {216 case VariableStreamKind.Error:217 AddToVarList(_errorVariableList, obj);218 break;219 case VariableStreamKind.Warning:220 AddToVarList(_warningVariableList, obj);221 break;222 case VariableStreamKind.Output:223 AddToVarList(_outVariableList, obj);224 break;225 case VariableStreamKind.Information:226 AddToVarList(_informationVariableList, obj);227 break;228 }229 }230 231 internal void AddVariableList(VariableStreamKind kind, IList list)232 {233 switch (kind)234 {235 case VariableStreamKind.Error:236 _errorVariableList ??= new List<IList>();237 238 _errorVariableList.Add(list);239 break;240 case VariableStreamKind.Warning:241 _warningVariableList ??= new List<IList>();242 243 _warningVariableList.Add(list);244 break;245 case VariableStreamKind.Output:246 _outVariableList ??= new List<IList>();247 248 _outVariableList.Add(list);249 break;250 case VariableStreamKind.Information:251 _informationVariableList ??= new List<IList>();252 253 _informationVariableList.Add(list);254 break;255 }256 }257 258 internal void SetPipelineVariable(PSVariable pipelineVariable)259 {260 _pipelineVariableObject = pipelineVariable;261 }262 263 internal void RemoveVariableList(VariableStreamKind kind, IList list)264 {265 switch (kind)266 {267 case VariableStreamKind.Error:268 _errorVariableList.Remove(list);269 break;270 case VariableStreamKind.Warning:271 _warningVariableList.Remove(list);272 break;273 case VariableStreamKind.Output:274 _outVariableList.Remove(list);275 break;276 case VariableStreamKind.Information:277 _informationVariableList.Remove(list);278 break;279 }280 }281 282 internal void RemovePipelineVariable()283 {284 if (_pipelineVariableObject != null)285 {286 _pipelineVariableObject.Value = null;287 _pipelineVariableObject = null;288 }289 }290 291 /// <summary>292 /// When a temporary pipe is used in the middle of execution, then we need to pass along293 /// the error and warning variable list to hold the errors and warnings get written out294 /// while the temporary pipe is being used.295 ///296 /// We don't need to pass along the out variable list because we don't care about the output297 /// generated in the middle of execution.298 /// </summary>299 internal void SetVariableListForTemporaryPipe(Pipe tempPipe)300 {301 CopyVariableToTempPipe(VariableStreamKind.Error, _errorVariableList, tempPipe);302 CopyVariableToTempPipe(VariableStreamKind.Warning, _warningVariableList, tempPipe);303 CopyVariableToTempPipe(VariableStreamKind.Information, _informationVariableList, tempPipe);304 }305 306 private static void CopyVariableToTempPipe(VariableStreamKind streamKind, List<IList> variableList, Pipe tempPipe)307 {308 if (variableList != null && variableList.Count > 0)309 {310 for (int i = 0; i < variableList.Count; i++)311 {312 tempPipe.AddVariableList(streamKind, variableList[i]);313 }314 }315 }316 317 #region ctor318 319 /// <summary>320 /// Default constructor - Creates the object queue.321 /// </summary>322 /// <remarks>323 /// The initial Queue capacity is 1, but it will grow automatically.324 /// </remarks>325 internal Pipe()326 {327 ObjectQueue = new Queue<object>();328 }329 330 /// <summary>331 /// This overload causes output to be written into a List.332 /// </summary>333 /// <param name="resultList"></param>334 internal Pipe(List<object> resultList)335 {336 Diagnostics.Assert(resultList != null, "resultList cannot be null");337 _isRedirected = true;338 _resultList = resultList;339 }340 341 private readonly List<object> _resultList;342 343 /// <summary>344 /// This overload causes output to be345 /// written onto an Collection[PSObject] which is more useful346 /// in many circumstances than arraylist.347 /// </summary>348 /// <param name="resultCollection">The collection to write into.</param>349 internal Pipe(System.Collections.ObjectModel.Collection<PSObject> resultCollection)350 {351 Diagnostics.Assert(resultCollection != null, "resultCollection cannot be null");352 _isRedirected = true;353 _resultCollection = resultCollection;354 }355 356 private readonly System.Collections.ObjectModel.Collection<PSObject> _resultCollection;357 358 /// <summary>359 /// This pipe writes into another pipeline processor allowing360 /// pipelines to be chained together...361 /// </summary>362 /// <param name="context">The execution context object for this engine instance.</param>363 /// <param name="outputPipeline">The pipeline to write into...</param>364 internal Pipe(ExecutionContext context, PipelineProcessor outputPipeline)365 {366 Diagnostics.Assert(outputPipeline != null, "outputPipeline cannot be null");367 Diagnostics.Assert(outputPipeline != null, "context cannot be null");368 _isRedirected = true;369 _context = context;370 PipelineProcessor = outputPipeline;371 }372 373 /// <summary>374 /// Read from an enumerator instead of a pipeline reader...375 /// </summary>376 /// <param name="enumeratorToProcess">The enumerator to process...</param>377 internal Pipe(IEnumerator enumeratorToProcess)378 {379 Diagnostics.Assert(enumeratorToProcess != null, "enumeratorToProcess cannot be null");380 _enumeratorToProcess = enumeratorToProcess;381 382 // since there is an enumerator specified, we383 // assume that there is some stuff to read384 _enumeratorToProcessIsEmpty = false;385 }386 387 private readonly IEnumerator _enumeratorToProcess;388 private bool _enumeratorToProcessIsEmpty;389 390 #endregion ctor391 392 /// <summary>393 /// Writes an object to the pipe. This could recursively call to the394 /// downstream cmdlet, or write the object to the external output.395 /// </summary>396 /// <param name="obj">The object to add to the pipe.</param>397 /// <remarks>398 /// AutomationNull.Value is ignored399 /// </remarks>400 /// <exception cref="PipelineStoppedException">401 /// a terminating error occurred, or the pipeline was otherwise stopped402 /// </exception>403 /// <exception cref="PipelineClosedException">404 /// The ExternalWriter stream is closed405 /// </exception>406 internal void Add(object obj)407 {408 if (obj == AutomationNull.Value)409 return;410 411 // OutVariable is appended for null pipes so that the following works:412 // foo -OutVariable bar > $null413 AddToVarList(_outVariableList, obj);414 415 if (_nullPipe)416 return;417 418 // Store the current pipeline variable419 if (_pipelineVariableObject != null)420 {421 _pipelineVariableObject.Value = obj;422 }423 424 AddToPipe(obj);425 }426 427 internal void AddWithoutAppendingOutVarList(object obj)428 {429 if (obj == AutomationNull.Value || _nullPipe)430 return;431 432 AddToPipe(obj);433 }434 435 private void AddToPipe(object obj)436 {437 if (PipelineProcessor != null)438 {439 // Put the pipeline on the notification stack for stop.440 _context.PushPipelineProcessor(PipelineProcessor);441 PipelineProcessor.Step(obj);442 _context.PopPipelineProcessor(false);443 }444 else if (_resultCollection != null)445 {446 _resultCollection.Add(obj != null ? PSObject.AsPSObject(obj) : null);447 }448 else if (_resultList != null)449 {450 _resultList.Add(obj);451 }452 else if (_externalWriter != null)453 {454 _externalWriter.Write(obj);455 }456 else if (ObjectQueue != null)457 {458 ObjectQueue.Enqueue(obj);459 460 // This is the "streamlet" recursive call461 if (_downstreamCmdlet != null && ObjectQueue.Count > OutBufferCount)462 {463 _downstreamCmdlet.DoExecute();464 }465 }466 }467 468 /// <summary>469 /// Writes a set of objects to the pipe. This could recursively470 /// call to the downstream cmdlet, or write the objects to the471 /// external output.472 /// </summary>473 /// <param name="objects">474 /// Each of the objects are added to the pipe475 /// </param>476 /// <exception cref="PipelineStoppedException">477 /// The pipeline has already been stopped,478 /// or a terminating error occurred in a downstream cmdlet.479 /// </exception>480 /// <exception cref="PipelineClosedException">481 /// The ExternalWriter stream is closed482 /// </exception>483 internal void AddItems(object objects)484 {485 // Use the extended type system to try and get an enumerator for the object being added.486 // If we get an enumerator, then add the individual elements. If the object isn't487 // enumerable (i.e. the call returned null) then add the object to the pipe488 // as a single element.489 IEnumerator ie = LanguagePrimitives.GetEnumerator(objects);490 try491 {492 if (ie == null)493 {494 Add(objects);495 }496 else497 {498 while (ParserOps.MoveNext(_context, null, ie))499 {500 object o = ParserOps.Current(null, ie);501 502 // Slip over any instance of AutomationNull.Value in the pipeline...503 if (o == AutomationNull.Value)504 {505 continue;506 }507 508 Add(o);509 }510 }511 }512 finally513 {514 // If our object came from GetEnumerator (and hence is not IEnumerator), then we need to dispose515 // Otherwise, we don't own the object, so don't dispose.516 var disposable = ie as IDisposable;517 if (disposable != null && objects is not IEnumerator)518 {519 disposable.Dispose();520 }521 }522 523 if (_externalWriter != null)524 return;525 526 // If there are objects waiting for the downstream command527 // call it now528 if (_downstreamCmdlet != null && ObjectQueue != null && ObjectQueue.Count > OutBufferCount)529 {530 _downstreamCmdlet.DoExecute();531 }532 }533 534 /// <summary>535 /// Returns an object from the pipe. If pipe is empty returns null.536 /// This will try the ExternalReader if there are no queued objects.537 /// </summary>538 /// <returns>539 /// object that is retrieved, or AutomationNull.Value if none540 /// </returns>541 internal object Retrieve()542 {543 if (ObjectQueue != null && ObjectQueue.Count != 0)544 {545 return ObjectQueue.Dequeue();546 }547 else if (_enumeratorToProcess != null)548 {549 if (_enumeratorToProcessIsEmpty)550 {551 return AutomationNull.Value;552 }553 554 while (true)555 {556 if (!ParserOps.MoveNext(_context, errorPosition: null, _enumeratorToProcess))557 {558 _enumeratorToProcessIsEmpty = true;559 return AutomationNull.Value;560 }561 562 object retValue = ParserOps.Current(errorPosition: null, _enumeratorToProcess);563 if (retValue == AutomationNull.Value)564 {565 // 'AutomationNull.Value' from the enumerator won't be sent to the pipeline.566 // We try to get the next value in this case.567 continue;568 }569 570 return retValue;571 }572 }573 else if (ExternalReader != null)574 {575 try576 {577 object o = ExternalReader.Read();578 if (AutomationNull.Value == o)579 {580 // NOTICE-2004/06/08-JonN 963367581 // The fix to this bug involves making one last582 // attempt to read from the pipeline in DoComplete.583 // We should be sure to not hit the ExternalReader584 // again if it already reported completion.585 ExternalReader = null;586 }587 588 return o;589 }590 catch (PipelineClosedException)591 {592 return AutomationNull.Value;593 }594 catch (ObjectDisposedException)595 {596 return AutomationNull.Value;597 }598 }599 else600 return AutomationNull.Value;601 }602 603 /// <summary>604 /// Removes all the objects from the Pipe.605 /// </summary>606 internal void Clear() => ObjectQueue?.Clear();607 608 /// <summary>609 /// Returns the currently queued items in the pipe. Note that this will610 /// not block on ExternalInput, and it does not modify the contents of611 /// the pipe.612 /// </summary>613 /// <returns>Possibly empty array of objects, but not null.</returns>614 internal object[] ToArray()615 {616 if (ObjectQueue == null || ObjectQueue.Count == 0)617 return MshCommandRuntime.StaticEmptyArray;618 619 return ObjectQueue.ToArray();620 }621 }622}623 