/********************************************************************++
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
}
}