/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands { /// /// Contains information about a single history entry /// public class HistoryInfo { #region constuctor /// /// Constructor /// /// Id of pipeline in which command associated /// with this history entry is executed /// command string /// status of pipeline execution /// startTime of execution /// endTime of execution internal HistoryInfo(long pipelineId, string cmdline, PipelineState status, DateTime startTime, DateTime endTime) { Dbg.Assert(cmdline != null, "caller should validate the parameter"); _pipelineId = pipelineId; _cmdline = cmdline; _status = status; _startTime = startTime; _endTime = endTime; _cleared = false; } /// /// Copy constructor to support cloning /// /// private HistoryInfo(HistoryInfo history) { _id = history._id; _pipelineId = history._pipelineId; _cmdline = history._cmdline; _status = history._status; _startTime = history._startTime; _endTime = history._endTime; _cleared = history._cleared; } #endregion constructor #region public /// /// Id of this history entry. /// /// public long Id { get { return _id; } } /// /// CommandLine string /// /// public string CommandLine { get { return _cmdline; } } /// /// Execution status of assoicated pipeline /// /// public PipelineState ExecutionStatus { get { return _status; } } /// /// Start time of execution of associated pipeline /// /// public DateTime StartExecutionTime { get { return _startTime; } } /// /// End time of execution of associated pipeline /// /// public DateTime EndExecutionTime { get { return _endTime; } } /// /// Override for ToString() method /// /// public override string ToString() { if (string.IsNullOrEmpty(_cmdline)) { return base.ToString(); } else { return _cmdline; } } #endregion public #region internal /// /// Cleared status of an entry /// internal bool Cleared { get { return _cleared; } set { _cleared = value; } } /// /// Sets Id /// /// internal void SetId(long id) { _id = id; } /// /// Set status /// /// internal void SetStatus(PipelineState status) { _status = status; } /// /// Set endtime /// /// internal void SetEndTime(DateTime endTime) { _endTime = endTime; } /// /// Sets command /// /// internal void SetCommand(string command) { _cmdline = command; } #endregion internal #region private /// /// Id of the pipeline corresponding to this history entry /// private long _pipelineId; /// /// Id of the history entry /// private long _id; /// /// CommandLine string /// private string _cmdline; /// /// ExecutionStatus of execution /// private PipelineState _status; /// /// Start time of execution /// private DateTime _startTime; /// ///End time of execution /// private DateTime _endTime; /// /// Flag indicating an entry is present/cleared /// private bool _cleared = false; #endregion private #region ICloneable Members /// /// Retuns a clone of this object /// /// public HistoryInfo Clone() { return new HistoryInfo(this); } #endregion } /// /// This class implements history and provides APIs for adding and fetching /// entries from history /// internal class History { /// /// Default history size /// internal const int DefaultHistorySize = 4096; #region constructors /// /// Constructs history store /// internal History(ExecutionContext context) { //Create history size variable. Add ValidateRangeAttribute to //validate the range. Collection attrs = new Collection(); attrs.Add(new ValidateRangeAttribute(1, (int)Int16.MaxValue)); PSVariable historySizeVar = new PSVariable(SpecialVariables.HistorySize, DefaultHistorySize, ScopedItemOptions.None, attrs); historySizeVar.Description = SessionStateStrings.MaxHistoryCountDescription; context.EngineSessionState.SetVariable(historySizeVar, false, CommandOrigin.Internal); _capacity = DefaultHistorySize; _buffer = new HistoryInfo[_capacity]; } #endregion constructors #region internal /// /// Create a new history entry /// /// /// /// /// /// /// If true, the entry will not be added when the history is locked /// id for the new created entry. Use this id to fetch the /// entry. Returns -1 if the entry is not added /// This function is thread safe internal long AddEntry(long pipelineId, string cmdline, PipelineState status, DateTime startTime, DateTime endTime, bool skipIfLocked) { if (!System.Threading.Monitor.TryEnter(_syncRoot, skipIfLocked ? 0 : System.Threading.Timeout.Infinite)) { return -1; } try { ReallocateBufferIfNeeded(); HistoryInfo entry = new HistoryInfo(pipelineId, cmdline, status, startTime, endTime); return Add(entry); } finally { System.Threading.Monitor.Exit(_syncRoot); } } /// /// Udpate the history entry corresponding to id. /// /// id of history entry to be updated /// status to be updated /// endTime to be updated /// If true, the entry will not be added when the history is locked /// internal void UpdateEntry(long id, PipelineState status, DateTime endTime, bool skipIfLocked) { if (!System.Threading.Monitor.TryEnter(_syncRoot, skipIfLocked ? 0 : System.Threading.Timeout.Infinite)) { return; } try { HistoryInfo entry = CoreGetEntry(id); if (entry != null) { entry.SetStatus(status); entry.SetEndTime(endTime); } } finally { System.Threading.Monitor.Exit(_syncRoot); } } /// /// Gets entry from buffer for given id. This id should be the /// id returned by Add method. /// /// Id of the entry to be fetched /// entry corresponding to id if it is present else null /// internal HistoryInfo GetEntry(long id) { lock (_syncRoot) { ReallocateBufferIfNeeded(); HistoryInfo entry = CoreGetEntry(id); if (entry != null) if (entry.Cleared == false) return entry.Clone(); return null; } } /// /// Get count HistoryEntries /// /// /// /// /// history entries internal HistoryInfo[] GetEntries(long id, long count, SwitchParameter newest) { ReallocateBufferIfNeeded(); if (count < -1) { throw PSTraceSource.NewArgumentOutOfRangeException("count", count); } if (newest.ToString() == null) { throw PSTraceSource.NewArgumentNullException("newest"); } if (count == -1 || count > _countEntriesAdded || count > _countEntriesInBuffer) count = _countEntriesInBuffer; if (count == 0 || _countEntriesInBuffer == 0) { return Utils.EmptyArray(); } lock (_syncRoot) { //Using list instead of an array to store the entries.With array we are getting null values //when the historybuffer size is changed List entriesList = new List(); if (id > 0) { long firstId, baseId; baseId = id; //get id,count,newest values if (!newest.IsPresent) { //get older entries //Calculate the first id (i.e lowest id to fetch) firstId = baseId - count + 1; //If first id is less than the lowest id in history store, //assign lowest id as first ID if (firstId < 1) { firstId = 1; } for (long i = baseId; i >= firstId; --i) { if (firstId <= 1) break; // if entry is null , continue the loop with the next entry if (_buffer[GetIndexFromId(i)] == null) continue; if (_buffer[GetIndexFromId(i)].Cleared == true) { // we have to clear count entries before an id, so if an entry is null,decrement // first id as long as its is greater than the lowest entry in the buffer. firstId--; continue; } } for (long i = firstId; i <= baseId; ++i) { //if an entry is null after being cleared by clear-history cmdlet, //continue with the next entry if (_buffer[GetIndexFromId(i)] == null || _buffer[GetIndexFromId(i)].Cleared == true) continue; entriesList.Add(_buffer[GetIndexFromId(i)].Clone()); } } else { //get latest entries // first id becomes the id +count no of entries from the end of the buffer firstId = baseId + count - 1; // if first id is more than the no of entries in the buffer, first id will be the last entry in the buffer if (firstId >= _countEntriesAdded) { firstId = _countEntriesAdded; } for (long i = baseId; i <= firstId; i++) { if (firstId >= _countEntriesAdded) break; // if entry is null , continue the loop with the next entry if (_buffer[GetIndexFromId(i)] == null) continue; if (_buffer[GetIndexFromId(i)].Cleared == true) { // we have to clear count entries before an id, so if an entry is null,increment first id firstId++; continue; } } for (long i = firstId; i >= baseId; --i) { //if an entry is null after being cleared by clear-history cmdlet, //continue with the next entry if (_buffer[GetIndexFromId(i)] == null || _buffer[GetIndexFromId(i)].Cleared == true) continue; entriesList.Add(_buffer[GetIndexFromId(i)].Clone()); } } } else { //get entries for count,newest long index, SmallestID = 0; //if we change the defaulthistory size and when no of entries exceed the size, then //we need to get the smallest entry in the buffer when we want to clear the oldest entry //eg if size is 5 and then the enrties can be 7,6,1,2,3 if (_capacity != DefaultHistorySize) SmallestID = SmallestIDinBuffer(); if (!newest.IsPresent) { //get oldest count entries index = 1; if (_capacity != DefaultHistorySize) { if (_countEntriesAdded > _capacity) index = SmallestID; } for (long i = count - 1; i >= 0;) { if (index > _countEntriesAdded) break; if ((index <= 0 || GetIndexFromId(index) >= _buffer.Length) || (_buffer[GetIndexFromId(index)].Cleared == true)) { index++; continue; } else { entriesList.Add(_buffer[GetIndexFromId(index)].Clone()); i--; index++; } } } else { index = _countEntriesAdded;//SmallestIDinBuffer for (long i = count - 1; i >= 0;) { // if an entry is cleared continue to the next entry if (_capacity != DefaultHistorySize) { if (_countEntriesAdded > _capacity) { if (index < SmallestID) break; } } if (index < 1) break; if ((index <= 0 || GetIndexFromId(index) >= _buffer.Length) || (_buffer[GetIndexFromId(index)].Cleared == true)) { index--; continue; } else { //clone the entry from the history buffer entriesList.Add(_buffer[GetIndexFromId(index)].Clone()); i--; index--; } } } } HistoryInfo[] entries = new HistoryInfo[entriesList.Count]; entriesList.CopyTo(entries); return entries; }// end lock }// end function /// /// Get History Entries based on the WildCard Pattern value. /// If passed 0, returns all the values, else return on the basis of count. /// /// /// /// /// internal HistoryInfo[] GetEntries(WildcardPattern wildcardpattern, long count, SwitchParameter newest) { lock (_syncRoot) { if (count < -1) { throw PSTraceSource.NewArgumentOutOfRangeException("count", count); } if (newest.ToString() == null) { throw PSTraceSource.NewArgumentNullException("newest"); } if (count > _countEntriesAdded || count == -1) { count = _countEntriesInBuffer; } List cmdlist = new List(); long SmallestID = 1; //if buffersize is changes,Get the smallest entry thts not cleared in the buffer if (_capacity != DefaultHistorySize) SmallestID = SmallestIDinBuffer(); if (count != 0) { if (!newest.IsPresent) { long id = 1; if (_capacity != DefaultHistorySize) { if (_countEntriesAdded > _capacity) id = SmallestID; } for (long i = 0; i <= count - 1;) { if (id > _countEntriesAdded) break; if (_buffer[GetIndexFromId(id)].Cleared == false && wildcardpattern.IsMatch(_buffer[GetIndexFromId(id)].CommandLine.Trim())) { cmdlist.Add(_buffer[GetIndexFromId(id)].Clone()); i++; } id++; } } else { long id = _countEntriesAdded; for (long i = 0; i <= count - 1;) { //if buffersize is changed,we have to loop from max enttry to min entry thats not cleraed if (_capacity != DefaultHistorySize) { if (_countEntriesAdded > _capacity) { if (id < SmallestID) break; } } if (id < 1) break; if (_buffer[GetIndexFromId(id)].Cleared == false && wildcardpattern.IsMatch(_buffer[GetIndexFromId(id)].CommandLine.Trim())) { cmdlist.Add(_buffer[GetIndexFromId(id)].Clone()); i++; } id--; } } } else { for (long i = 1; i <= _countEntriesAdded; i++) { if (_buffer[GetIndexFromId(i)].Cleared == false && wildcardpattern.IsMatch(_buffer[GetIndexFromId(i)].CommandLine.Trim())) { cmdlist.Add(_buffer[GetIndexFromId(i)].Clone()); } } } HistoryInfo[] entries = new HistoryInfo[cmdlist.Count]; cmdlist.CopyTo(entries); return entries; } } /// /// Clears the history entry from buffer for a given id. /// /// Id of the entry to be Cleared /// nothing internal void ClearEntry(long id) { lock (_syncRoot) { if (id < 0) { throw PSTraceSource.NewArgumentOutOfRangeException("id", id); } // no entries are present to clear if (_countEntriesInBuffer == 0) return; // throw an exception if id is out of range if (id > _countEntriesAdded) { return; } HistoryInfo entry = CoreGetEntry(id); if (entry != null) { entry.Cleared = true; _countEntriesInBuffer--; } return; } } /// /// gets the total number of entries added /// ///count of total entries added internal int Buffercapacity() { return _capacity; } #endregion internal #region private /// /// Adds an entry to the buffer. If buffer is full, overwrites /// oldest entry in the buffer /// /// /// Returns id for the entry. This id should be used to fetch /// the entry from the buffer /// Id starts from 1 and is incremented by 1 for each new entry private long Add(HistoryInfo entry) { if (entry == null) { throw PSTraceSource.NewArgumentNullException("entry"); } _buffer[GetIndexForNewEntry()] = entry; //Increment count of entries added so far _countEntriesAdded++; //Id of an entry in history is same as its number in history store. entry.SetId(_countEntriesAdded); //Increment count of entries in buffer by 1 IncrementCountOfEntriesInBuffer(); return _countEntriesAdded; } /// /// Gets entry from buffer for given id. This id should be the /// id returned by Add method. /// /// Id of the entry to be fetched /// entry corresponding to id if it is present else null /// private HistoryInfo CoreGetEntry(long id) { if (id <= 0) { throw PSTraceSource.NewArgumentOutOfRangeException("id", id); } if (_countEntriesInBuffer == 0) return null; if (id > _countEntriesAdded) { return null; } // if (_buffer[GetIndexFromId(id)].Cleared == false ) return _buffer[GetIndexFromId(id)]; // else // return null; } /// /// Gets the smallest id in the buffer /// /// private long SmallestIDinBuffer() { long minID = 0; if (_buffer == null) return minID; for (int i = 0; i < _buffer.Length; i++) { //assign the first entry in the buffer as min. if (_buffer[i] != null && _buffer[i].Cleared == false) { minID = _buffer[i].Id; break; } } //check for the minimum id that is not cleared for (int i = 0; i < _buffer.Length; i++) { if (_buffer[i] != null && _buffer[i].Cleared == false) if (minID > _buffer[i].Id) minID = _buffer[i].Id; } return minID; } /// /// Reallocates the buffer if history size changed /// private void ReallocateBufferIfNeeded() { //Get current value of histoysize variable int historySize = GetHistorySize(); if (historySize == _capacity) return; HistoryInfo[] tempBuffer = new HistoryInfo[historySize]; //Calculate number of entries to copy in new buffer. int numberOfEntries = _countEntriesInBuffer; //when buffer size is changed,we have to consider the totalnumber of entries added if (numberOfEntries < _countEntriesAdded) numberOfEntries = (int)_countEntriesAdded; if (_countEntriesInBuffer > historySize) numberOfEntries = historySize; for (int i = numberOfEntries; i > 0; --i) { long nextId = _countEntriesAdded - i + 1; tempBuffer[GetIndexFromId(nextId, historySize)] = _buffer[GetIndexFromId(nextId)]; } _countEntriesInBuffer = numberOfEntries; _capacity = historySize; _buffer = tempBuffer; } /// /// Get the index for new entry /// /// Index for new entry private int GetIndexForNewEntry() { return (int)(_countEntriesAdded % _capacity); } /// /// Gets index in buffer for an entry with given Id /// /// private int GetIndexFromId(long id) { return (int)((id - 1) % _capacity); } /// /// Gets index in buffer for an entry with given Id using passed in /// capacity /// /// /// /// private static int GetIndexFromId(long id, int capacity) { return (int)((id - 1) % capacity); } /// /// Increment number of entries in buffer by 1 /// private void IncrementCountOfEntriesInBuffer() { if (_countEntriesInBuffer < _capacity) _countEntriesInBuffer++; } /// /// Get the current history size /// /// private int GetHistorySize() { int historySize = 0; var executionContext = LocalPipeline.GetExecutionContextFromTLS(); object obj = (executionContext != null) ? executionContext.GetVariableValue(SpecialVariables.HistorySizeVarPath) : null; if (obj != null) { try { historySize = (int)LanguagePrimitives.ConvertTo(obj, typeof(int), System.Globalization.CultureInfo.InvariantCulture); } catch (InvalidCastException) { } } if (historySize <= 0) { historySize = DefaultHistorySize; } return historySize; } /// /// buffer /// private HistoryInfo[] _buffer; /// /// Capacity of circular buffer /// private int _capacity; /// /// Number of entries in buffer currently /// private int _countEntriesInBuffer; /// /// total number of entries added till now including those which have /// been overwritten after buffer got full. This is also number of /// last entry added. /// private long _countEntriesAdded; /// /// Private object for synchronization /// private object _syncRoot = new object(); #endregion private /// /// return the ID of the next history item to be added /// internal long GetNextHistoryId() { return _countEntriesAdded + 1; } } /// /// This class Implements the get-history command /// [Cmdlet(VerbsCommon.Get, "History", HelpUri = "http://go.microsoft.com/fwlink/?LinkID=113317")] [OutputType(typeof(HistoryInfo))] public class GetHistoryCommand : PSCmdlet { /// /// Ids of entries to display /// private long[] _id; /// /// Ids of entries to display /// /// [Parameter(Position = 0, ValueFromPipeline = true)] [ValidateRangeAttribute((long)1, long.MaxValue)] public long[] Id { get { return _id; } set { _id = value; } } /// /// Is Count parameter specified /// private bool _countParameterSpecified; /// /// Count of entries to display. By default, count is the length of the history buffer. /// So "Get-History" returns all history entries. /// private int _count; /// /// No of History Entries (starting from last) that are to be displayed. /// [Parameter(Position = 1)] [ValidateRangeAttribute(0, (int)Int16.MaxValue)] public int Count { get { return _count; } set { _countParameterSpecified = true; _count = value; } } /// /// Implements the Processing() method for show/History command /// protected override void ProcessRecord() { History history = ((LocalRunspace)Context.CurrentRunspace).History; if (_id != null) { if (!_countParameterSpecified) { //If Id parameter is specified and count is not specified, //get history foreach (long id in _id) { Dbg.Assert(id > 0, "ValidateRangeAttribute should not allow this"); HistoryInfo entry = history.GetEntry(id); if (entry != null && entry.Id == id) { WriteObject(entry); } else { Exception ex = new ArgumentException ( StringUtil.Format(HistoryStrings.NoHistoryForId, id) ); WriteError ( new ErrorRecord ( ex, "GetHistoryNoHistoryForId", ErrorCategory.ObjectNotFound, id ) ); } } } else if (_id.Length > 1) { Exception ex = new ArgumentException ( StringUtil.Format(HistoryStrings.NoCountWithMultipleIds) ); ThrowTerminatingError ( new ErrorRecord ( ex, "GetHistoryNoCountWithMultipleIds", ErrorCategory.InvalidArgument, _count ) ); } else { long id = _id[0]; Dbg.Assert(id > 0, "ValidateRangeAttribute should not allow this"); WriteObject(history.GetEntries(id, _count, false), true); } } else { // The defualt value for _count is the size of the history buffer. if (!_countParameterSpecified) { _count = history.Buffercapacity(); } HistoryInfo[] entries = history.GetEntries(0, _count, true); for (long i = entries.Length - 1; i >= 0; i--) WriteObject(entries[i]); } } } /// /// This class implements the Invoke-History command /// [Cmdlet("Invoke", "History", SupportsShouldProcess = true, HelpUri = "http://go.microsoft.com/fwlink/?LinkID=113344")] public class InvokeHistoryCommand : PSCmdlet { #region Parameters /// /// Invoke cmd can execute only one history entry. If multiple /// ids are provided, we throw error. /// private bool _multipleIdProvided; private string _id; /// /// Accepts a string value indicating a previously executed command to /// re-execute. /// If string can be parsed to long, /// it will be used as HistoryId /// else /// as a string value indicating a previously executed command to /// re-execute. This string is the first n characters of the command /// that is to be re-executed. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true)] public string Id { get { return _id; } set { if (_id != null) { //Id has been set already. _multipleIdProvided = true; } _id = value; } } #endregion /// /// Implements the BeginProcessing() method for eval/History command /// protected override void EndProcessing() { //Invoke-history can execute only one command. If multiple //ids were provided, throw exception if (_multipleIdProvided == true) { Exception ex = new ArgumentException ( StringUtil.Format(HistoryStrings.InvokeHistoryMultipleCommandsError) ); ThrowTerminatingError ( new ErrorRecord ( ex, "InvokeHistoryMultipleCommandsError", ErrorCategory.InvalidArgument, null ) ); } History history = ((LocalRunspace)Context.CurrentRunspace).History; Dbg.Assert(history != null, "History should be non null"); //Get the history entry to invoke HistoryInfo entry = GetHistoryEntryToInvoke(history); //Check if there is a loop in invoke-history LocalPipeline pipeline = (LocalPipeline)((LocalRunspace)Context.CurrentRunspace).GetCurrentlyRunningPipeline(); if (pipeline.PresentInInvokeHistoryEntryList(entry) == false) { pipeline.AddToInvokeHistoryEntryList(entry); } else { Exception ex = new InvalidOperationException ( StringUtil.Format(HistoryStrings.InvokeHistoryLoopDetected) ); ThrowTerminatingError ( new ErrorRecord ( ex, "InvokeHistoryLoopDetected", ErrorCategory.InvalidOperation, null ) ); } //Replace Invoke-History with string which is getting invoked ReplaceHistoryString(entry); //Now invoke the command string commandToInvoke = entry.CommandLine; if (ShouldProcess(commandToInvoke) == false) { return; } try { //Echo command Host.UI.WriteLine(commandToInvoke); } catch (HostException) { //when the host is not interactive, HostException is thrown //do nothing } // Items invoked as History should act as though they were submitted by the user - so should still come from // the runspace itself. For this reason, it is insufficient to just use the InvokeScript method on the Cmdlet class. using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace)) { ps.AddScript(commandToInvoke); EventHandler debugAdded = delegate (Object sender, DataAddedEventArgs e) { DebugRecord record = (DebugRecord)((PSDataCollection)sender)[e.Index]; WriteDebug(record.Message); }; EventHandler errorAdded = delegate (Object sender, DataAddedEventArgs e) { ErrorRecord record = (ErrorRecord)((PSDataCollection)sender)[e.Index]; WriteError(record); }; EventHandler informationAdded = delegate (Object sender, DataAddedEventArgs e) { InformationRecord record = (InformationRecord)((PSDataCollection)sender)[e.Index]; WriteInformation(record); }; EventHandler progressAdded = delegate (Object sender, DataAddedEventArgs e) { ProgressRecord record = (ProgressRecord)((PSDataCollection)sender)[e.Index]; WriteProgress(record); }; EventHandler verboseAdded = delegate (Object sender, DataAddedEventArgs e) { VerboseRecord record = (VerboseRecord)((PSDataCollection)sender)[e.Index]; WriteVerbose(record.Message); }; EventHandler warningAdded = delegate (Object sender, DataAddedEventArgs e) { WarningRecord record = (WarningRecord)((PSDataCollection)sender)[e.Index]; WriteWarning(record.Message); }; ps.Streams.Debug.DataAdded += debugAdded; ps.Streams.Error.DataAdded += errorAdded; ps.Streams.Information.DataAdded += informationAdded; ps.Streams.Progress.DataAdded += progressAdded; ps.Streams.Verbose.DataAdded += verboseAdded; ps.Streams.Warning.DataAdded += warningAdded; try { Collection results = ps.Invoke(); if (results.Count > 0) { WriteObject(results, true); } pipeline.RemoveFromInvokeHistoryEntryList(entry); } finally { ps.Streams.Debug.DataAdded -= debugAdded; ps.Streams.Error.DataAdded -= errorAdded; ps.Streams.Information.DataAdded -= informationAdded; ps.Streams.Progress.DataAdded -= progressAdded; ps.Streams.Verbose.DataAdded -= verboseAdded; ps.Streams.Warning.DataAdded -= warningAdded; } } } /// /// Helper function which gets history entry to invoke /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "It's ok to use ID in the ArgumentException")] private HistoryInfo GetHistoryEntryToInvoke(History history) { HistoryInfo entry = null; //User didn't specify any input parameter. Invoke the last //entry if (_id == null) { HistoryInfo[] entries = history.GetEntries(0, 1, true); if (entries.Length == 1) { entry = entries[0]; } else { Exception ex = new InvalidOperationException ( StringUtil.Format(HistoryStrings.NoLastHistoryEntryFound) ); ThrowTerminatingError ( new ErrorRecord ( ex, "InvokeHistoryNoLastHistoryEntryFound", ErrorCategory.InvalidOperation, null ) ); } } else { //Parse input PopulateIdAndCommandLine(); //User specified a commandline. Get list of all history entries //and find latest match if (_commandLine != null) { HistoryInfo[] entries = history.GetEntries(0, -1, false); // and search backwards through the entries for (int i = entries.Length - 1; i >= 0; i--) { if (entries[i].CommandLine.StartsWith(_commandLine, StringComparison.CurrentCulture)) { entry = entries[i]; break; } } if (entry == null) { Exception ex = new ArgumentException ( StringUtil.Format(HistoryStrings.NoHistoryForCommandline, _commandLine) ); ThrowTerminatingError ( new ErrorRecord ( ex, "InvokeHistoryNoHistoryForCommandline", ErrorCategory.ObjectNotFound, _commandLine ) ); } } else { if (_historyId <= 0) { Exception ex = new ArgumentOutOfRangeException ( "Id", StringUtil.Format(HistoryStrings.InvalidIdGetHistory, _historyId) ); ThrowTerminatingError ( new ErrorRecord ( ex, "InvokeHistoryInvalidIdGetHistory", ErrorCategory.InvalidArgument, _historyId ) ); } else { //Retrieve the command at the index we've specified entry = history.GetEntry(_historyId); if (entry == null || entry.Id != _historyId) { Exception ex = new ArgumentException ( StringUtil.Format(HistoryStrings.NoHistoryForId, _historyId) ); ThrowTerminatingError ( new ErrorRecord ( ex, "InvokeHistoryNoHistoryForId", ErrorCategory.ObjectNotFound, _historyId ) ); } } } } return entry; } /// /// Id of history entry to execute. /// private long _historyId = -1; /// /// Commandline to execute. /// private string _commandLine; /// /// Parse Id parameter to populate _historyId and _commandLine /// private void PopulateIdAndCommandLine() { if (_id == null) return; try { _historyId = (long)LanguagePrimitives.ConvertTo(_id, typeof(long), System.Globalization.CultureInfo.InvariantCulture); } catch (PSInvalidCastException) { _commandLine = _id; return; } } /// /// Invoke-history is replaced in history by the command it executed. /// This replacement happens only if Invoke-History is single element /// in the pipeline. If there are more than one element in pipeline /// (ex A | Invoke-History 2 | B) then we cannot do this replacement. /// private void ReplaceHistoryString(HistoryInfo entry) { //Get the current pipeline LocalPipeline pipeline = (LocalPipeline)((LocalRunspace)Context.CurrentRunspace).GetCurrentlyRunningPipeline(); if (pipeline.AddToHistory) { pipeline.HistoryString = entry.CommandLine; } } } /// /// This class Implements the add-history command /// [Cmdlet(VerbsCommon.Add, "History", HelpUri = "http://go.microsoft.com/fwlink/?LinkID=113279")] [OutputType(typeof(HistoryInfo))] public class AddHistoryCommand : PSCmdlet { #region parameters /// /// This parameter specifies the current pipeline object /// [Parameter(Position = 0, ValueFromPipeline = true)] public PSObject[] InputObject { set; get; } private bool _passthru; /// /// A Boolean that indicates whether history objects should be /// passed to the next element in the pipeline. /// [Parameter()] public SwitchParameter Passthru { get { return _passthru; } set { _passthru = value; } } #endregion parameters /// /// override for BeginProcessing /// protected override void BeginProcessing() { //Get currently running pipeline and add history entry for //this pipeline. //Note:Generally History entry for current pipeline is added //on completion of pipeline (See LocalPipeline implementation). //However Add-history adds additional entries in to history and //additional entries must be added after history for current pipeline. //This is done by adding the history entry for current pipeline below. LocalPipeline lpl = (LocalPipeline)((RunspaceBase)Context.CurrentRunspace).GetCurrentlyRunningPipeline(); lpl.AddHistoryEntryFromAddHistoryCmdlet(); } /// /// override for ProcessRecord /// protected override void ProcessRecord() { History history = ((LocalRunspace)Context.CurrentRunspace).History; Dbg.Assert(history != null, "History should be non null"); if (InputObject != null) { foreach (PSObject input in InputObject) { //Wrap the inputobject in PSObject and convert it to //HistoryInfo object. HistoryInfo infoToAdd = GetHistoryInfoObject(input); if (infoToAdd != null) { long id = history.AddEntry ( 0, infoToAdd.CommandLine, infoToAdd.ExecutionStatus, infoToAdd.StartExecutionTime, infoToAdd.EndExecutionTime, false ); if (Passthru) { HistoryInfo infoAdded = history.GetEntry(id); WriteObject(infoAdded); } } } } } /// /// Convert mshObject that has has the properties of an HistoryInfo /// object in to HistoryInfo object. /// /// /// mshObject to be converted to HistoryInfo. /// /// /// HistoryInfo object if coversion is successful else null. /// #pragma warning disable 0162 private HistoryInfo GetHistoryInfoObject(PSObject mshObject) { do { if (mshObject == null) { break; } //Read CommandLine property string commandLine = GetPropertyValue(mshObject, "CommandLine") as string; if (commandLine == null) { break; } //Read ExecutionStatus property object pipelineState = GetPropertyValue(mshObject, "ExecutionStatus"); if (pipelineState == null) { break; } PipelineState executionStatus; if (pipelineState is PipelineState) { executionStatus = (PipelineState)pipelineState; } else if (pipelineState is PSObject) { PSObject serializedPipelineState = pipelineState as PSObject; object baseObject = serializedPipelineState.BaseObject; if (!(baseObject is int)) { break; } executionStatus = (PipelineState)baseObject; if (executionStatus < PipelineState.NotStarted || executionStatus > PipelineState.Failed) { break; } } else if (pipelineState is string) { try { executionStatus = (PipelineState)Enum.Parse(typeof(PipelineState), (string)pipelineState); } catch (ArgumentException) { break; } } else { break; } //Read StartExecutionTime property DateTime startExecutionTime; object temp = GetPropertyValue(mshObject, "StartExecutionTime"); if (temp == null) { break; } else if (temp is DateTime) { startExecutionTime = (DateTime)temp; } else if (temp is string) { try { startExecutionTime = DateTime.Parse((string)temp, System.Globalization.CultureInfo.CurrentCulture); } catch (FormatException) { break; } } else { break; } //Read EndExecutionTime property DateTime endExecutionTime; temp = GetPropertyValue(mshObject, "EndExecutionTime"); if (temp == null) { break; } else if (temp is DateTime) { endExecutionTime = (DateTime)temp; } else if (temp is string) { try { endExecutionTime = DateTime.Parse((string)temp, System.Globalization.CultureInfo.CurrentCulture); } catch (FormatException) { break; } } else { break; } return new HistoryInfo ( 0, commandLine, executionStatus, startExecutionTime, endExecutionTime ); } while (false); //If we are here, an error has occured. Exception ex = new InvalidDataException ( StringUtil.Format(HistoryStrings.AddHistoryInvalidInput) ); WriteError ( new ErrorRecord ( ex, "AddHistoryInvalidInput", ErrorCategory.InvalidData, mshObject ) ); return null; } #pragma warning restore 0162 private static object GetPropertyValue(PSObject mshObject, string propertyName) { PSMemberInfo propertyInfo = mshObject.Properties[propertyName]; if (propertyInfo == null) return null; return propertyInfo.Value; } } /// /// This Class implements the Clear History cmdlet /// [Cmdlet(VerbsCommon.Clear, "History", SupportsShouldProcess = true, DefaultParameterSetName = "IDParameter", HelpUri = "http://go.microsoft.com/fwlink/?LinkID=135199")] public class ClearHistoryCommand : PSCmdlet { #region Command Line Parameters /// /// Specifies the ID of a command in the session history.Clear history clears the entries /// wit the specified ID(s) /// [Parameter(ParameterSetName = "IDParameter", Position = 0, HelpMessage = "Specifies the ID of a command in the session history.Clear history clears only the specified command")] [ValidateRangeAttribute((int)1, int.MaxValue)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] Id { get { return _id; } set { _id = value; } } /// /// id of a history entry /// private int[] _id; /// /// command line name of an entry in the session history /// [Parameter(ParameterSetName = "CommandLineParameter", HelpMessage = "Specifies the name of a command in the session history")] [ValidateNotNullOrEmpty()] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] CommandLine { get { return _commandline; } set { _commandline = value; } } /// /// commandline parameter /// private string[] _commandline = null; /// /// Clears the specified number of history entries /// [Parameter(Mandatory = false, Position = 1, HelpMessage = "Clears the specified number of history entries")] [ValidateRangeAttribute((int)1, int.MaxValue)] public int Count { get { return _count; } set { _countParamterSpecified = true; _count = value; } } /// /// count of the history entries /// private int _count = 32; /// /// a boolean variable to indicate if the count parameter specified /// private bool _countParamterSpecified = false; /// /// Specifies whether new entries to be cleared or the default old ones. /// [Parameter(Mandatory = false, HelpMessage = "Specifies whether new entries to be cleared or the default old ones.")] public SwitchParameter Newest { get { return _newest; } set { _newest = value; } } /// /// switch parameter on the history entries /// private SwitchParameter _newest; #endregion Command Line Parameters /// /// Overriding Begin Processing /// protected override void BeginProcessing() { _history = ((LocalRunspace)Context.CurrentRunspace).History; } /// /// Overriding Process Record /// protected override void ProcessRecord() { //case statement to identify the parameter set switch (ParameterSetName.ToString()) { case "IDParameter": ClearHistoryByID(); break; case "CommandLineParameter": ClearHistoryByCmdLine(); break; default: ThrowTerminatingError( new ErrorRecord( new ArgumentException("Invalid ParameterSet Name"), "Unable to access the session history", ErrorCategory.InvalidOperation, null)); return; } } #region Private /// /// Clears the session history based on the id parameter /// takes no parameters /// nothing /// private void ClearHistoryByID() { if (_countParamterSpecified && Count < 0) { Exception ex = new ArgumentException ( StringUtil.Format("HistoryStrings", "InvalidCountValue") ); ThrowTerminatingError ( new ErrorRecord ( ex, "ClearHistoryInvalidCountValue", ErrorCategory.InvalidArgument, _count ) ); } // if id parameter is not present if (_id != null) { // if count parameter is not present if (!_countParamterSpecified) { // clearing the entry for each id in the id[] parameter. foreach (long id in _id) { Dbg.Assert(id > 0, "ValidateRangeAttribute should not allow this"); HistoryInfo entry = _history.GetEntry(id); if (entry != null && entry.Id == id) { _history.ClearEntry(entry.Id); } else {// throw an exception if an entry for an id is not found Exception ex = new ArgumentException ( StringUtil.Format(HistoryStrings.NoHistoryForId, id) ); WriteError ( new ErrorRecord ( ex, "GetHistoryNoHistoryForId", ErrorCategory.ObjectNotFound, id ) ); } } } else if (_id.Length > 1) {// throwing an exception for invalid parameter combinations Exception ex = new ArgumentException ( StringUtil.Format(HistoryStrings.NoCountWithMultipleIds) ); ThrowTerminatingError ( new ErrorRecord ( ex, "GetHistoryNoCountWithMultipleIds", ErrorCategory.InvalidArgument, _count ) ); } else {// if id,count adn newest parameters are present //throw an exception for invalid count values long id = _id[0]; Dbg.Assert(id > 0, "ValidateRangeAttribute should not allow this"); ClearHistoryEntries(id, _count, null, _newest); } } else { // confirmation message if all the clearhistory cmdlet is used without any parameters if (_countParamterSpecified == false) { string message = StringUtil.Format(HistoryStrings.ClearHistoryWarning, "Warning");// "The command would clear all the entry(s) from the session history,Are you sure you want to continue ?"; if (!ShouldProcess(message)) { return; } ClearHistoryEntries(0, -1, null, _newest); } else { ClearHistoryEntries(0, _count, null, _newest); } } } /// /// Clears the session history based on the Commandline parameter /// takes no parameters /// nothing /// private void ClearHistoryByCmdLine() { // throw an exception for invalid count values if (_countParamterSpecified && Count < 0) { Exception ex = new ArgumentException ( StringUtil.Format(HistoryStrings.InvalidCountValue) ); ThrowTerminatingError ( new ErrorRecord ( ex, "ClearHistoryInvalidCountValue", ErrorCategory.InvalidArgument, _count ) ); } // if command line is not present if (_commandline != null) { // if count parameter is not present if (!_countParamterSpecified) { foreach (string cmd in _commandline) { ClearHistoryEntries(0, 1, cmd, _newest); } } else if (_commandline.Length > 1) {// throwing exceptions for invalid parameter combinations Exception ex = new ArgumentException ( StringUtil.Format(HistoryStrings.NoCountWithMultipleCmdLine) ); ThrowTerminatingError ( new ErrorRecord ( ex, "NoCountWithMultipleCmdLine ", ErrorCategory.InvalidArgument, _commandline ) ); } else { // if commandline,count and newest parameters are present. ClearHistoryEntries(0, _count, _commandline[0], _newest); } } } /// /// Clears the session history based on the input parametera /// id of the entry to be cleared /// count of entries to be cleared /// cmdline string to be cleared /// order of the entries /// nothing /// private void ClearHistoryEntries(long id, int count, string cmdline, SwitchParameter newest) { // if cmdline is null,use default parameter set notion. if (cmdline == null) { // if id is present,clears count entries from id if (id > 0) { HistoryInfo entry = _history.GetEntry(id); if (entry == null || entry.Id != id) { Exception ex = new ArgumentException ( StringUtil.Format(HistoryStrings.NoHistoryForId, id) ); WriteError ( new ErrorRecord ( ex, "GetHistoryNoHistoryForId", ErrorCategory.ObjectNotFound, id ) ); } _entries = _history.GetEntries(id, count, newest); } else {// if only count is present _entries = _history.GetEntries(0, count, newest); } } else { //creates a wild card pattern WildcardPattern wildcardpattern = WildcardPattern.Get(cmdline, WildcardOptions.IgnoreCase); //count set to zero if not specified. if (!_countParamterSpecified && WildcardPattern.ContainsWildcardCharacters(cmdline)) { count = 0; } // Return the matching history entries for the command line parameter // if newest id false...gets the oldest entry _entries = _history.GetEntries(wildcardpattern, count, newest); }// end case cmdline //Clear the History value. foreach (HistoryInfo entry in _entries) { if (entry != null && entry.Cleared == false) _history.ClearEntry(entry.Id); } return; }//end function /// /// history obj /// private History _history; /// /// array of historyinfo objects /// private HistoryInfo[] _entries; #endregion Private } }