MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Diagnostics.CodeAnalysis;8using System.Globalization;9using System.IO;10using System.Management.Automation;11using System.Management.Automation.Host;12using System.Management.Automation.Internal;13using System.Management.Automation.Runspaces;14 15using Dbg = System.Management.Automation.Diagnostics;16 17namespace Microsoft.PowerShell.Commands18{19 /// <summary>20 /// Contains information about a single history entry.21 /// </summary>22 public class HistoryInfo23 {24 /// <summary>25 /// Constructor.26 /// </summary>27 /// <param name="pipelineId">Id of pipeline in which command associated28 /// with this history entry is executed</param>29 /// <param name="cmdline">Command string.</param>30 /// <param name="status">Status of pipeline execution.</param>31 /// <param name="startTime">StartTime of execution.</param>32 /// <param name="endTime">EndTime of execution.</param>33 internal HistoryInfo(long pipelineId, string cmdline, PipelineState status, DateTime startTime, DateTime endTime)34 {35 Dbg.Assert(cmdline != null, "caller should validate the parameter");36 _pipelineId = pipelineId;37 CommandLine = cmdline;38 ExecutionStatus = status;39 StartExecutionTime = startTime;40 EndExecutionTime = endTime;41 Cleared = false;42 }43 44 /// <summary>45 /// Copy constructor to support cloning.46 /// </summary>47 /// <param name="history"></param>48 private HistoryInfo(HistoryInfo history)49 {50 Id = history.Id;51 _pipelineId = history._pipelineId;52 CommandLine = history.CommandLine;53 ExecutionStatus = history.ExecutionStatus;54 StartExecutionTime = history.StartExecutionTime;55 EndExecutionTime = history.EndExecutionTime;56 Cleared = history.Cleared;57 }58 59 /// <summary>60 /// Id of this history entry.61 /// </summary>62 /// <value></value>63 public long Id { get; private set; }64 65 /// <summary>66 /// CommandLine string.67 /// </summary>68 /// <value></value>69 public string CommandLine { get; private set; }70 71 /// <summary>72 /// Execution status of associated pipeline.73 /// </summary>74 /// <value></value>75 public PipelineState ExecutionStatus { get; private set; }76 77 /// <summary>78 /// Start time of execution of associated pipeline.79 /// </summary>80 /// <value></value>81 public DateTime StartExecutionTime { get; }82 83 /// <summary>84 /// End time of execution of associated pipeline.85 /// </summary>86 /// <value></value>87 public DateTime EndExecutionTime { get; private set; }88 89 /// <summary>90 /// The time it took to execute the associeated pipeline.91 /// </summary>92 public TimeSpan Duration => EndExecutionTime - StartExecutionTime;93 94 /// <summary>95 /// Override for ToString() method.96 /// </summary>97 /// <returns></returns>98 public override string ToString()99 {100 if (string.IsNullOrEmpty(CommandLine))101 {102 return base.ToString();103 }104 else105 {106 return CommandLine;107 }108 }109 110 /// <summary>111 /// Cleared status of an entry.112 /// </summary>113 internal bool Cleared { get; set; } = false;114 115 /// <summary>116 /// Sets Id.117 /// </summary>118 /// <param name="id"></param>119 internal void SetId(long id) => Id = id;120 121 /// <summary>122 /// Set status.123 /// </summary>124 /// <param name="status"></param>125 internal void SetStatus(PipelineState status) => ExecutionStatus = status;126 127 /// <summary>128 /// Set endtime.129 /// </summary>130 /// <param name="endTime"></param>131 internal void SetEndTime(DateTime endTime) => EndExecutionTime = endTime;132 133 /// <summary>134 /// Sets command.135 /// </summary>136 /// <param name="command"></param>137 internal void SetCommand(string command) => CommandLine = command;138 139 /// <summary>140 /// Id of the pipeline corresponding to this history entry.141 /// </summary>142 private readonly long _pipelineId;143 144 /// <summary>145 /// Returns a clone of this object.146 /// </summary>147 /// <returns></returns>148 public HistoryInfo Clone()149 {150 return new HistoryInfo(this);151 }152 }153 154 /// <summary>155 /// This class implements history and provides APIs for adding and fetching156 /// entries from history.157 /// </summary>158 internal class History159 {160 /// <summary>161 /// Default history size.162 /// </summary>163 internal const int DefaultHistorySize = 4096;164 165 #region constructors166 167 /// <summary>168 /// Constructs history store.169 /// </summary>170 internal History(ExecutionContext context)171 {172 // Create history size variable. Add ValidateRangeAttribute to173 // validate the range.174 Collection<Attribute> attrs = new Collection<Attribute>();175 attrs.Add(new ValidateRangeAttribute(1, (int)Int16.MaxValue));176 PSVariable historySizeVar = new PSVariable(SpecialVariables.HistorySize, DefaultHistorySize, ScopedItemOptions.None, attrs);177 historySizeVar.Description = SessionStateStrings.MaxHistoryCountDescription;178 179 context.EngineSessionState.SetVariable(historySizeVar, false, CommandOrigin.Internal);180 181 _capacity = DefaultHistorySize;182 _buffer = new HistoryInfo[_capacity];183 }184 185 #endregion constructors186 187 #region internal188 189 /// <summary>190 /// Create a new history entry.191 /// </summary>192 /// <param name="pipelineId"></param>193 /// <param name="cmdline"></param>194 /// <param name="status"></param>195 /// <param name="startTime"></param>196 /// <param name="endTime"></param>197 /// <param name="skipIfLocked">If true, the entry will not be added when the history is locked.</param>198 /// <returns>Id for the new created entry. Use this id to fetch the199 /// entry. Returns -1 if the entry is not added.</returns>200 /// <remarks>This function is thread safe</remarks>201 internal long AddEntry(long pipelineId, string cmdline, PipelineState status, DateTime startTime, DateTime endTime, bool skipIfLocked)202 {203 if (!System.Threading.Monitor.TryEnter(_syncRoot, skipIfLocked ? 0 : System.Threading.Timeout.Infinite))204 {205 return -1;206 }207 208 try209 {210 ReallocateBufferIfNeeded();211 212 HistoryInfo entry = new HistoryInfo(pipelineId, cmdline, status, startTime, endTime);213 return Add(entry);214 }215 finally216 {217 System.Threading.Monitor.Exit(_syncRoot);218 }219 }220 221 /// <summary>222 /// Update the history entry corresponding to id.223 /// </summary>224 /// <param name="id">Id of history entry to be updated.</param>225 /// <param name="status">Status to be updated.</param>226 /// <param name="endTime">EndTime to be updated.</param>227 /// <param name="skipIfLocked">If true, the entry will not be added when the history is locked.</param>228 /// <returns></returns>229 internal void UpdateEntry(long id, PipelineState status, DateTime endTime, bool skipIfLocked)230 {231 if (!System.Threading.Monitor.TryEnter(_syncRoot, skipIfLocked ? 0 : System.Threading.Timeout.Infinite))232 {233 return;234 }235 236 try237 {238 HistoryInfo entry = CoreGetEntry(id);239 if (entry != null)240 {241 entry.SetStatus(status);242 entry.SetEndTime(endTime);243 }244 }245 finally246 {247 System.Threading.Monitor.Exit(_syncRoot);248 }249 }250 251 /// <summary>252 /// Gets entry from buffer for given id. This id should be the253 /// id returned by Add method.254 /// </summary>255 /// <param name="id">Id of the entry to be fetched.</param>256 /// <returns>Entry corresponding to id if it is present else null257 /// </returns>258 internal HistoryInfo GetEntry(long id)259 {260 lock (_syncRoot)261 {262 ReallocateBufferIfNeeded();263 264 HistoryInfo entry = CoreGetEntry(id);265 if (entry != null)266 if (!entry.Cleared)267 return entry.Clone();268 269 return null;270 }271 }272 273 /// <summary>274 /// Get count HistoryEntries.275 /// </summary>276 /// <param name="id"></param>277 /// <param name="count"></param>278 /// <param name="newest"></param>279 /// <returns>History entries.</returns>280 internal HistoryInfo[] GetEntries(long id, long count, SwitchParameter newest)281 {282 ReallocateBufferIfNeeded();283 284 if (count < -1)285 {286 throw PSTraceSource.NewArgumentOutOfRangeException(nameof(count), count);287 }288 289 if (newest.ToString() == null)290 {291 throw PSTraceSource.NewArgumentNullException(nameof(newest));292 }293 294 if (count == -1 || count > _countEntriesAdded || count > _countEntriesInBuffer)295 count = _countEntriesInBuffer;296 297 if (count == 0 || _countEntriesInBuffer == 0)298 {299 return Array.Empty<HistoryInfo>();300 }301 302 lock (_syncRoot)303 {304 // Using list instead of an array to store the entries.With array we are getting null values305 // when the historybuffer size is changed306 List<HistoryInfo> entriesList = new List<HistoryInfo>();307 if (id > 0)308 {309 long firstId, baseId;310 baseId = id;311 312 // get id,count,newest values313 if (!newest.IsPresent)314 {315 // get older entries316 317 // Calculate the first id (i.e lowest id to fetch)318 firstId = baseId - count + 1;319 320 // If first id is less than the lowest id in history store,321 // assign lowest id as first ID322 if (firstId < 1)323 {324 firstId = 1;325 }326 327 for (long i = baseId; i >= firstId; --i)328 {329 if (firstId <= 1) break;330 // if entry is null , continue the loop with the next entry331 if (_buffer[GetIndexFromId(i)] == null) continue;332 if (_buffer[GetIndexFromId(i)].Cleared)333 {334 // we have to clear count entries before an id, so if an entry is null,decrement335 // first id as long as its is greater than the lowest entry in the buffer.336 firstId--;337 continue;338 }339 }340 341 for (long i = firstId; i <= baseId; ++i)342 {343 // if an entry is null after being cleared by clear-history cmdlet,344 // continue with the next entry345 if (_buffer[GetIndexFromId(i)] == null || _buffer[GetIndexFromId(i)].Cleared)346 continue;347 entriesList.Add(_buffer[GetIndexFromId(i)].Clone());348 }349 }350 else351 { // get latest entries352 // first id becomes the id +count no of entries from the end of the buffer353 firstId = baseId + count - 1;354 // if first id is more than the no of entries in the buffer, first id will be the last entry in the buffer355 if (firstId >= _countEntriesAdded)356 {357 firstId = _countEntriesAdded;358 }359 360 for (long i = baseId; i <= firstId; i++)361 {362 if (firstId >= _countEntriesAdded) break;363 // if entry is null , continue the loop with the next entry364 if (_buffer[GetIndexFromId(i)] == null) continue;365 if (_buffer[GetIndexFromId(i)].Cleared)366 {367 // we have to clear count entries before an id, so if an entry is null,increment first id368 firstId++;369 continue;370 }371 }372 373 for (long i = firstId; i >= baseId; --i)374 {375 // if an entry is null after being cleared by clear-history cmdlet,376 // continue with the next entry377 if (_buffer[GetIndexFromId(i)] == null || _buffer[GetIndexFromId(i)].Cleared)378 continue;379 entriesList.Add(_buffer[GetIndexFromId(i)].Clone());380 }381 }382 }383 else384 {385 // get entries for count,newest386 387 long index, SmallestID = 0;388 // if we change the defaulthistory size and when no of entries exceed the size, then389 // we need to get the smallest entry in the buffer when we want to clear the oldest entry390 // eg if size is 5 and then the entries can be 7,6,1,2,3391 if (_capacity != DefaultHistorySize)392 SmallestID = SmallestIDinBuffer();393 if (!newest.IsPresent)394 {395 // get oldest count entries396 index = 1;397 if (_capacity != DefaultHistorySize)398 {399 if (_countEntriesAdded > _capacity)400 index = SmallestID;401 }402 403 for (long i = count - 1; i >= 0;)404 {405 if (index > _countEntriesAdded) break;406 if ((index <= 0 || GetIndexFromId(index) >= _buffer.Length) ||407 (_buffer[GetIndexFromId(index)].Cleared))408 {409 index++; continue;410 }411 else412 {413 entriesList.Add(_buffer[GetIndexFromId(index)].Clone());414 i--; index++;415 }416 }417 }418 else419 {420 index = _countEntriesAdded; //SmallestIDinBuffer421 422 for (long i = count - 1; i >= 0;)423 {424 // if an entry is cleared continue to the next entry425 if (_capacity != DefaultHistorySize)426 {427 if (_countEntriesAdded > _capacity)428 {429 if (index < SmallestID)430 break;431 }432 }433 434 if (index < 1) break;435 if ((index <= 0 || GetIndexFromId(index) >= _buffer.Length) ||436 (_buffer[GetIndexFromId(index)].Cleared))437 { index--; continue; }438 else439 {440 // clone the entry from the history buffer441 entriesList.Add(_buffer[GetIndexFromId(index)].Clone());442 i--; index--;443 }444 }445 }446 }447 448 HistoryInfo[] entries = new HistoryInfo[entriesList.Count];449 entriesList.CopyTo(entries);450 return entries;451 }452 }453 454 /// <summary>455 /// Get History Entries based on the WildCard Pattern value.456 /// If passed 0, returns all the values, else return on the basis of count.457 /// </summary>458 /// <param name="wildcardpattern"></param>459 /// <param name="count"></param>460 /// <param name="newest"></param>461 /// <returns></returns>462 internal HistoryInfo[] GetEntries(WildcardPattern wildcardpattern, long count, SwitchParameter newest)463 {464 lock (_syncRoot)465 {466 if (count < -1)467 {468 throw PSTraceSource.NewArgumentOutOfRangeException(nameof(count), count);469 }470 471 if (newest.ToString() == null)472 {473 throw PSTraceSource.NewArgumentNullException(nameof(newest));474 }475 476 if (count > _countEntriesAdded || count == -1)477 {478 count = _countEntriesInBuffer;479 }480 481 List<HistoryInfo> cmdlist = new List<HistoryInfo>();482 long SmallestID = 1;483 // if buffersize is changes,Get the smallest entry that's not cleared in the buffer484 if (_capacity != DefaultHistorySize)485 SmallestID = SmallestIDinBuffer();486 if (count != 0)487 {488 if (!newest.IsPresent)489 {490 long id = 1;491 if (_capacity != DefaultHistorySize)492 {493 if (_countEntriesAdded > _capacity)494 id = SmallestID;495 }496 497 for (long i = 0; i <= count - 1;)498 {499 if (id > _countEntriesAdded) break;500 if (!_buffer[GetIndexFromId(id)].Cleared && wildcardpattern.IsMatch(_buffer[GetIndexFromId(id)].CommandLine.Trim()))501 {502 cmdlist.Add(_buffer[GetIndexFromId(id)].Clone()); i++;503 }504 505 id++;506 }507 }508 else509 {510 long id = _countEntriesAdded;511 for (long i = 0; i <= count - 1;)512 {513 // if buffersize is changed,we have to loop from max entry to min entry that's not cleared514 if (_capacity != DefaultHistorySize)515 {516 if (_countEntriesAdded > _capacity)517 {518 if (id < SmallestID)519 break;520 }521 }522 523 if (id < 1) break;524 if (!_buffer[GetIndexFromId(id)].Cleared && wildcardpattern.IsMatch(_buffer[GetIndexFromId(id)].CommandLine.Trim()))525 {526 cmdlist.Add(_buffer[GetIndexFromId(id)].Clone()); i++;527 }528 529 id--;530 }531 }532 }533 else534 {535 for (long i = 1; i <= _countEntriesAdded; i++)536 {537 if (!_buffer[GetIndexFromId(i)].Cleared && wildcardpattern.IsMatch(_buffer[GetIndexFromId(i)].CommandLine.Trim()))538 {539 cmdlist.Add(_buffer[GetIndexFromId(i)].Clone());540 }541 }542 }543 544 HistoryInfo[] entries = new HistoryInfo[cmdlist.Count];545 cmdlist.CopyTo(entries);546 return entries;547 }548 }549 550 /// <summary>551 /// Clears the history entry from buffer for a given id.552 /// </summary>553 /// <param name="id">Id of the entry to be Cleared.</param>554 /// <returns>Nothing.</returns>555 internal void ClearEntry(long id)556 {557 lock (_syncRoot)558 {559 if (id < 0)560 {561 throw PSTraceSource.NewArgumentOutOfRangeException(nameof(id), id);562 }563 // no entries are present to clear564 if (_countEntriesInBuffer == 0)565 return;566 567 // throw an exception if id is out of range568 if (id > _countEntriesAdded)569 {570 return;571 }572 573 HistoryInfo entry = CoreGetEntry(id);574 if (entry != null)575 {576 entry.Cleared = true;577 _countEntriesInBuffer--;578 }579 580 return;581 }582 }583 584 /// <summary>585 /// gets the total number of entries added586 /// </summary>587 /// <returns>count of total entries added.</returns>588 internal int Buffercapacity()589 {590 return _capacity;591 }592 593 #endregion internal594 595 #region private596 597 /// <summary>598 /// Adds an entry to the buffer. If buffer is full, overwrites599 /// oldest entry in the buffer.600 /// </summary>601 /// <param name="entry"></param>602 /// <returns>Returns id for the entry. This id should be used to fetch603 /// the entry from the buffer.</returns>604 /// <remarks>Id starts from 1 and is incremented by 1 for each new entry</remarks>605 private long Add(HistoryInfo entry)606 {607 if (entry == null)608 {609 throw PSTraceSource.NewArgumentNullException(nameof(entry));610 }611 612 _buffer[GetIndexForNewEntry()] = entry;613 614 // Increment count of entries added so far615 _countEntriesAdded++;616 617 // Id of an entry in history is same as its number in history store.618 entry.SetId(_countEntriesAdded);619 620 // Increment count of entries in buffer by 1621 IncrementCountOfEntriesInBuffer();622 623 return _countEntriesAdded;624 }625 626 /// <summary>627 /// Gets entry from buffer for given id. This id should be the628 /// id returned by Add method.629 /// </summary>630 /// <param name="id">Id of the entry to be fetched.</param>631 /// <returns>Entry corresponding to id if it is present else null632 /// </returns>633 private HistoryInfo CoreGetEntry(long id)634 {635 if (id <= 0)636 {637 throw PSTraceSource.NewArgumentOutOfRangeException(nameof(id), id);638 }639 640 if (_countEntriesInBuffer == 0)641 return null;642 if (id > _countEntriesAdded)643 {644 return null;645 }646 // if (_buffer[GetIndexFromId(id)].Cleared == false )647 return _buffer[GetIndexFromId(id)];648 // else649 // return null;650 }651 652 /// <summary>653 /// Gets the smallest id in the buffer.654 /// </summary>655 /// <returns></returns>656 private long SmallestIDinBuffer()657 {658 long minID = 0;659 if (_buffer == null)660 return minID;661 for (int i = 0; i < _buffer.Length; i++)662 {663 // assign the first entry in the buffer as min.664 if (_buffer[i] != null && !_buffer[i].Cleared)665 {666 minID = _buffer[i].Id;667 break;668 }669 }670 // check for the minimum id that is not cleared671 for (int i = 0; i < _buffer.Length; i++)672 {673 if (_buffer[i] != null && !_buffer[i].Cleared)674 if (minID > _buffer[i].Id)675 minID = _buffer[i].Id;676 }677 678 return minID;679 }680 681 /// <summary>682 /// Reallocates the buffer if history size changed.683 /// </summary>684 private void ReallocateBufferIfNeeded()685 {686 // Get current value of histoysize variable687 int historySize = GetHistorySize();688 689 if (historySize == _capacity)690 return;691 692 HistoryInfo[] tempBuffer = new HistoryInfo[historySize];693 694 // Calculate number of entries to copy in new buffer.695 int numberOfEntries = _countEntriesInBuffer;696 697 // when buffer size is changed,we have to consider the totalnumber of entries added698 if (numberOfEntries < _countEntriesAdded)699 numberOfEntries = (int)_countEntriesAdded;700 701 if (_countEntriesInBuffer > historySize)702 numberOfEntries = historySize;703 704 for (int i = numberOfEntries; i > 0; --i)705 {706 long nextId = _countEntriesAdded - i + 1;707 708 tempBuffer[GetIndexFromId(nextId, historySize)] = _buffer[GetIndexFromId(nextId)];709 }710 711 _countEntriesInBuffer = numberOfEntries;712 _capacity = historySize;713 _buffer = tempBuffer;714 }715 716 /// <summary>717 /// Get the index for new entry.718 /// </summary>719 /// <returns>Index for new entry.</returns>720 private int GetIndexForNewEntry()721 {722 return (int)(_countEntriesAdded % _capacity);723 }724 725 /// <summary>726 /// Gets index in buffer for an entry with given Id.727 /// </summary>728 /// <returns></returns>729 private int GetIndexFromId(long id)730 {731 return (int)((id - 1) % _capacity);732 }733 734 /// <summary>735 /// Gets index in buffer for an entry with given Id using passed in736 /// capacity.737 /// </summary>738 /// <param name="id"></param>739 /// <param name="capacity"></param>740 /// <returns></returns>741 private static int GetIndexFromId(long id, int capacity)742 {743 return (int)((id - 1) % capacity);744 }745 746 /// <summary>747 /// Increment number of entries in buffer by 1.748 /// </summary>749 private void IncrementCountOfEntriesInBuffer()750 {751 if (_countEntriesInBuffer < _capacity)752 _countEntriesInBuffer++;753 }754 755 /// <summary>756 /// Get the current history size.757 /// </summary>758 /// <returns></returns>759 private static int GetHistorySize()760 {761 int historySize = 0;762 var executionContext = LocalPipeline.GetExecutionContextFromTLS();763 object obj = executionContext?.GetVariableValue(SpecialVariables.HistorySizeVarPath);764 if (obj != null)765 {766 try767 {768 historySize = (int)LanguagePrimitives.ConvertTo(obj, typeof(int), System.Globalization.CultureInfo.InvariantCulture);769 }770 catch (InvalidCastException)771 { }772 }773 774 if (historySize <= 0)775 {776 historySize = DefaultHistorySize;777 }778 779 return historySize;780 }781 782 /// <summary>783 /// Buffer.784 /// </summary>785 private HistoryInfo[] _buffer;786 787 /// <summary>788 /// Capacity of circular buffer.789 /// </summary>790 private int _capacity;791 792 /// <summary>793 /// Number of entries in buffer currently.794 /// </summary>795 private int _countEntriesInBuffer;796 797 /// <summary>798 /// Total number of entries added till now including those which have799 /// been overwritten after buffer got full. This is also number of800 /// last entry added.801 /// </summary>802 private long _countEntriesAdded;803 804 /// <summary>805 /// Private object for synchronization.806 /// </summary>807 private readonly object _syncRoot = new object();808 809 #endregion private810 811 /// <summary>812 /// Return the ID of the next history item to be added.813 /// </summary>814 internal long GetNextHistoryId()815 {816 return _countEntriesAdded + 1;817 }818 819 #region invoke_loop_detection820 821 /// <summary>822 /// This is a set of HistoryInfo ids which are currently being executed in the823 /// pipelines of the Runspace that is holding this 'History' instance.824 /// </summary>825 private readonly HashSet<long> _invokeHistoryIds = new HashSet<long>();826 827 internal bool PresentInInvokeHistoryEntrySet(HistoryInfo entry)828 {829 return _invokeHistoryIds.Contains(entry.Id);830 }831 832 internal void AddToInvokeHistoryEntrySet(HistoryInfo entry)833 {834 _invokeHistoryIds.Add(entry.Id);835 }836 837 internal void RemoveFromInvokeHistoryEntrySet(HistoryInfo entry)838 {839 _invokeHistoryIds.Remove(entry.Id);840 }841 842 #endregion invoke_loop_detection843 }844 845 /// <summary>846 /// This class Implements the get-history command.847 /// </summary>848 [Cmdlet(VerbsCommon.Get, "History", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096788")]849 [OutputType(typeof(HistoryInfo))]850 public class GetHistoryCommand : PSCmdlet851 {852 /// <summary>853 /// Ids of entries to display.854 /// </summary>855 private long[] _id;856 857 /// <summary>858 /// Ids of entries to display.859 /// </summary>860 /// <value></value>861 [Parameter(Position = 0, ValueFromPipeline = true)]862 [ValidateRange((long)1, long.MaxValue)]863 public long[] Id864 {865 get866 {867 return _id;868 }869 870 set871 {872 _id = value;873 }874 }875 876 /// <summary>877 /// Is Count parameter specified.878 /// </summary>879 private bool _countParameterSpecified;880 /// <summary>881 /// Count of entries to display. By default, count is the length of the history buffer.882 /// So "Get-History" returns all history entries.883 /// </summary>884 private int _count;885 886 /// <summary>887 /// No of History Entries (starting from last) that are to be displayed.888 /// </summary>889 [Parameter(Position = 1)]890 [ValidateRange(0, (int)Int16.MaxValue)]891 public int Count892 {893 get894 {895 return _count;896 }897 898 set899 {900 _countParameterSpecified = true;901 _count = value;902 }903 }904 905 /// <summary>906 /// Implements the Processing() method for show/History command.907 /// </summary>908 protected override void ProcessRecord()909 {910 History history = ((LocalRunspace)Context.CurrentRunspace).History;911 912 if (_id != null)913 {914 if (!_countParameterSpecified)915 {916 // If Id parameter is specified and count is not specified,917 // get history918 foreach (long id in _id)919 {920 Dbg.Assert(id > 0, "ValidateRangeAttribute should not allow this");921 922 HistoryInfo entry = history.GetEntry(id);923 924 if (entry != null && entry.Id == id)925 {926 WriteObject(entry);927 }928 else929 {930 Exception ex =931 new ArgumentException932 (933 StringUtil.Format(HistoryStrings.NoHistoryForId, id)934 );935 936 WriteError937 (938 new ErrorRecord939 (940 ex,941 "GetHistoryNoHistoryForId",942 ErrorCategory.ObjectNotFound,943 id944 )945 );946 }947 }948 }949 else if (_id.Length > 1)950 {951 Exception ex =952 new ArgumentException953 (954 StringUtil.Format(HistoryStrings.NoCountWithMultipleIds)955 );956 957 ThrowTerminatingError958 (959 new ErrorRecord960 (961 ex,962 "GetHistoryNoCountWithMultipleIds",963 ErrorCategory.InvalidArgument,964 _count965 )966 );967 }968 else969 {970 long id = _id[0];971 972 Dbg.Assert(id > 0, "ValidateRangeAttribute should not allow this");973 WriteObject(history.GetEntries(id, _count, false), true);974 }975 }976 else977 {978 // The default value for _count is the size of the history buffer.979 if (!_countParameterSpecified)980 {981 _count = history.Buffercapacity();982 }983 984 HistoryInfo[] entries = history.GetEntries(0, _count, true);985 for (long i = entries.Length - 1; i >= 0; i--)986 WriteObject(entries[i]);987 }988 }989 }990 991 /// <summary>992 /// This class implements the Invoke-History command.993 /// </summary>994 [Cmdlet(VerbsLifecycle.Invoke, "History", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096586")]995 public class InvokeHistoryCommand : PSCmdlet996 {997 #region Parameters998 999 /// <summary>1000 /// Invoke cmd can execute only one history entry. If multiple1001 /// ids are provided, we throw error.1002 /// </summary>1003 private bool _multipleIdProvided;1004 private string _id;1005 /// <summary>1006 /// Accepts a string value indicating a previously executed command to1007 /// re-execute.1008 /// If string can be parsed to long,1009 /// it will be used as HistoryId1010 /// else1011 /// as a string value indicating a previously executed command to1012 /// re-execute. This string is the first n characters of the command1013 /// that is to be re-executed.1014 /// </summary>1015 [Parameter(Position = 0, ValueFromPipelineByPropertyName = true)]1016 public string Id1017 {1018 get1019 {1020 return _id;1021 }1022 1023 set1024 {1025 if (_id != null)1026 {1027 // Id has been set already.1028 _multipleIdProvided = true;1029 }1030 1031 _id = value;1032 }1033 }1034 1035 #endregion1036 1037 /// <summary>1038 /// Implements the BeginProcessing() method for eval/History command.1039 /// </summary>1040 protected override void EndProcessing()1041 {1042 // Invoke-history can execute only one command. If multiple1043 // ids were provided, throw exception1044 if (_multipleIdProvided)1045 {1046 ThrowTerminatingError(1047 new ErrorRecord(1048 new ArgumentException(HistoryStrings.InvokeHistoryMultipleCommandsError),1049 "InvokeHistoryMultipleCommandsError",1050 ErrorCategory.InvalidArgument,1051 targetObject: null));1052 }1053 1054 var ctxRunspace = (LocalRunspace)Context.CurrentRunspace;1055 History history = ctxRunspace.History;1056 Dbg.Assert(history != null, "History should be non null");1057 1058 // Get the history entry to invoke1059 HistoryInfo entry = GetHistoryEntryToInvoke(history);1060 string commandToInvoke = entry.CommandLine;1061 1062 if (!ShouldProcess(commandToInvoke))1063 {1064 return;1065 }1066 1067 // Check if there is a loop in invoke-history1068 if (history.PresentInInvokeHistoryEntrySet(entry))1069 {1070 ThrowTerminatingError(1071 new ErrorRecord(1072 new InvalidOperationException(HistoryStrings.InvokeHistoryLoopDetected),1073 "InvokeHistoryLoopDetected",1074 ErrorCategory.InvalidOperation,1075 targetObject: null));1076 }1077 else1078 {1079 history.AddToInvokeHistoryEntrySet(entry);1080 }1081 1082 // Replace Invoke-History with string which is getting invoked1083 ReplaceHistoryString(entry, ctxRunspace);1084 1085 try1086 {1087 // Echo command1088 Host.UI.WriteLine(commandToInvoke);1089 }1090 catch (HostException)1091 {1092 // when the host is not interactive, HostException is thrown1093 // do nothing1094 }1095 1096 // Items invoked as History should act as though they were submitted by the user - so should still come from1097 // the runspace itself. For this reason, it is insufficient to just use the InvokeScript method on the Cmdlet class.1098 using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace))1099 {1100 ps.AddScript(commandToInvoke);1101 1102 EventHandler<DataAddedEventArgs> debugAdded = (object sender, DataAddedEventArgs e) => { DebugRecord record = (DebugRecord)((PSDataCollection<DebugRecord>)sender)[e.Index]; WriteDebug(record.Message); };1103 EventHandler<DataAddedEventArgs> errorAdded = (object sender, DataAddedEventArgs e) => { ErrorRecord record = (ErrorRecord)((PSDataCollection<ErrorRecord>)sender)[e.Index]; WriteError(record); };1104 EventHandler<DataAddedEventArgs> informationAdded = (object sender, DataAddedEventArgs e) => { InformationRecord record = (InformationRecord)((PSDataCollection<InformationRecord>)sender)[e.Index]; WriteInformation(record); };1105 EventHandler<DataAddedEventArgs> progressAdded = (object sender, DataAddedEventArgs e) => { ProgressRecord record = (ProgressRecord)((PSDataCollection<ProgressRecord>)sender)[e.Index]; WriteProgress(record); };1106 EventHandler<DataAddedEventArgs> verboseAdded = (object sender, DataAddedEventArgs e) => { VerboseRecord record = (VerboseRecord)((PSDataCollection<VerboseRecord>)sender)[e.Index]; WriteVerbose(record.Message); };1107 EventHandler<DataAddedEventArgs> warningAdded = (object sender, DataAddedEventArgs e) => { WarningRecord record = (WarningRecord)((PSDataCollection<WarningRecord>)sender)[e.Index]; WriteWarning(record.Message); };1108 1109 ps.Streams.Debug.DataAdded += debugAdded;1110 ps.Streams.Error.DataAdded += errorAdded;1111 ps.Streams.Information.DataAdded += informationAdded;1112 ps.Streams.Progress.DataAdded += progressAdded;1113 ps.Streams.Verbose.DataAdded += verboseAdded;1114 ps.Streams.Warning.DataAdded += warningAdded;1115 1116 LocalRunspace localRunspace = ps.Runspace as LocalRunspace;1117 1118 try1119 {1120 // Indicate to the system that we are in nested prompt mode, since we are emulating running the command at the prompt.1121 // This ensures that the command being run as nested runs in the correct language mode, because CreatePipelineProcessor()1122 // always forces CommandOrigin to Internal for nested running commands, and Command.CreateCommandProcessor() forces Internal1123 // commands to always run in FullLanguage mode unless in a nested prompt.1124 if (localRunspace != null)1125 {1126 localRunspace.InInternalNestedPrompt = ps.IsNested;1127 }1128 1129 Collection<PSObject> results = ps.Invoke();1130 if (results.Count > 0)1131 {1132 WriteObject(results, true);1133 }1134 }1135 finally1136 {1137 history.RemoveFromInvokeHistoryEntrySet(entry);1138 1139 if (localRunspace != null)1140 {1141 localRunspace.InInternalNestedPrompt = false;1142 }1143 1144 ps.Streams.Debug.DataAdded -= debugAdded;1145 ps.Streams.Error.DataAdded -= errorAdded;1146 ps.Streams.Information.DataAdded -= informationAdded;1147 ps.Streams.Progress.DataAdded -= progressAdded;1148 ps.Streams.Verbose.DataAdded -= verboseAdded;1149 ps.Streams.Warning.DataAdded -= warningAdded;1150 }1151 }1152 }1153 1154 /// <summary>1155 /// Helper function which gets history entry to invoke.1156 /// </summary>1157 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "It's ok to use ID in the ArgumentException")]1158 private HistoryInfo GetHistoryEntryToInvoke(History history)1159 {1160 HistoryInfo entry = null;1161 1162 // User didn't specify any input parameter. Invoke the last1163 // entry1164 if (_id == null)1165 {1166 HistoryInfo[] entries = history.GetEntries(0, 1, true);1167 1168 if (entries.Length == 1)1169 {1170 entry = entries[0];1171 }1172 else1173 {1174 Exception ex =1175 new InvalidOperationException1176 (1177 StringUtil.Format(HistoryStrings.NoLastHistoryEntryFound)1178 );1179 1180 ThrowTerminatingError1181 (1182 new ErrorRecord1183 (1184 ex,1185 "InvokeHistoryNoLastHistoryEntryFound",1186 ErrorCategory.InvalidOperation,1187 null1188 )1189 );1190 }1191 }1192 else1193 {1194 // Parse input1195 PopulateIdAndCommandLine();1196 // User specified a commandline. Get list of all history entries1197 // and find latest match1198 if (_commandLine != null)1199 {1200 HistoryInfo[] entries = history.GetEntries(0, -1, false);