mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
1992 lines
70 KiB
C#
1992 lines
70 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
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
|
|
{
|
|
/// <summary>
|
|
/// Contains information about a single history entry.
|
|
/// </summary>
|
|
public class HistoryInfo
|
|
{
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="pipelineId">Id of pipeline in which command associated
|
|
/// with this history entry is executed</param>
|
|
/// <param name="cmdline">Command string.</param>
|
|
/// <param name="status">Status of pipeline execution.</param>
|
|
/// <param name="startTime">StartTime of execution.</param>
|
|
/// <param name="endTime">EndTime of execution.</param>
|
|
internal HistoryInfo(long pipelineId, string cmdline, PipelineState status, DateTime startTime, DateTime endTime)
|
|
{
|
|
Dbg.Assert(cmdline != null, "caller should validate the parameter");
|
|
_pipelineId = pipelineId;
|
|
CommandLine = cmdline;
|
|
ExecutionStatus = status;
|
|
StartExecutionTime = startTime;
|
|
EndExecutionTime = endTime;
|
|
Cleared = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy constructor to support cloning.
|
|
/// </summary>
|
|
/// <param name="history"></param>
|
|
private HistoryInfo(HistoryInfo history)
|
|
{
|
|
Id = history.Id;
|
|
_pipelineId = history._pipelineId;
|
|
CommandLine = history.CommandLine;
|
|
ExecutionStatus = history.ExecutionStatus;
|
|
StartExecutionTime = history.StartExecutionTime;
|
|
EndExecutionTime = history.EndExecutionTime;
|
|
Cleared = history.Cleared;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Id of this history entry.
|
|
/// </summary>
|
|
/// <value></value>
|
|
public long Id { get; private set; }
|
|
|
|
/// <summary>
|
|
/// CommandLine string.
|
|
/// </summary>
|
|
/// <value></value>
|
|
public string CommandLine { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Execution status of associated pipeline.
|
|
/// </summary>
|
|
/// <value></value>
|
|
public PipelineState ExecutionStatus { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Start time of execution of associated pipeline.
|
|
/// </summary>
|
|
/// <value></value>
|
|
public DateTime StartExecutionTime { get; }
|
|
|
|
/// <summary>
|
|
/// End time of execution of associated pipeline.
|
|
/// </summary>
|
|
/// <value></value>
|
|
public DateTime EndExecutionTime { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The time it took to execute the associeated pipeline.
|
|
/// </summary>
|
|
public TimeSpan Duration => EndExecutionTime - StartExecutionTime;
|
|
|
|
/// <summary>
|
|
/// Override for ToString() method.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override string ToString()
|
|
{
|
|
if (string.IsNullOrEmpty(CommandLine))
|
|
{
|
|
return base.ToString();
|
|
}
|
|
else
|
|
{
|
|
return CommandLine;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleared status of an entry.
|
|
/// </summary>
|
|
|
|
internal bool Cleared { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Sets Id.
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
internal void SetId(long id) => Id = id;
|
|
|
|
/// <summary>
|
|
/// Set status.
|
|
/// </summary>
|
|
/// <param name="status"></param>
|
|
internal void SetStatus(PipelineState status) => ExecutionStatus = status;
|
|
|
|
/// <summary>
|
|
/// Set endtime.
|
|
/// </summary>
|
|
/// <param name="endTime"></param>
|
|
internal void SetEndTime(DateTime endTime) => EndExecutionTime = endTime;
|
|
|
|
/// <summary>
|
|
/// Sets command.
|
|
/// </summary>
|
|
/// <param name="command"></param>
|
|
internal void SetCommand(string command) => CommandLine = command;
|
|
|
|
/// <summary>
|
|
/// Id of the pipeline corresponding to this history entry.
|
|
/// </summary>
|
|
private long _pipelineId;
|
|
|
|
|
|
/// <summary>
|
|
/// Returns a clone of this object.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public HistoryInfo Clone()
|
|
{
|
|
return new HistoryInfo(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class implements history and provides APIs for adding and fetching
|
|
/// entries from history.
|
|
/// </summary>
|
|
internal class History
|
|
{
|
|
/// <summary>
|
|
/// Default history size.
|
|
/// </summary>
|
|
internal const int DefaultHistorySize = 4096;
|
|
|
|
#region constructors
|
|
|
|
/// <summary>
|
|
/// Constructs history store.
|
|
/// </summary>
|
|
|
|
internal History(ExecutionContext context)
|
|
{
|
|
// Create history size variable. Add ValidateRangeAttribute to
|
|
// validate the range.
|
|
Collection<Attribute> attrs = new Collection<Attribute>();
|
|
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
|
|
|
|
/// <summary>
|
|
/// Create a new history entry.
|
|
/// </summary>
|
|
/// <param name="pipelineId"></param>
|
|
/// <param name="cmdline"></param>
|
|
/// <param name="status"></param>
|
|
/// <param name="startTime"></param>
|
|
/// <param name="endTime"></param>
|
|
/// <param name="skipIfLocked">If true, the entry will not be added when the history is locked.</param>
|
|
/// <returns>Id for the new created entry. Use this id to fetch the
|
|
/// entry. Returns -1 if the entry is not added.</returns>
|
|
/// <remarks>This function is thread safe</remarks>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the history entry corresponding to id.
|
|
/// </summary>
|
|
/// <param name="id">Id of history entry to be updated.</param>
|
|
/// <param name="status">Status to be updated.</param>
|
|
/// <param name="endTime">EndTime to be updated.</param>
|
|
/// <param name="skipIfLocked">If true, the entry will not be added when the history is locked.</param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets entry from buffer for given id. This id should be the
|
|
/// id returned by Add method.
|
|
/// </summary>
|
|
/// <param name="id">Id of the entry to be fetched.</param>
|
|
/// <returns>Entry corresponding to id if it is present else null
|
|
/// </returns>
|
|
internal HistoryInfo GetEntry(long id)
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
ReallocateBufferIfNeeded();
|
|
|
|
HistoryInfo entry = CoreGetEntry(id);
|
|
if (entry != null)
|
|
if (entry.Cleared == false)
|
|
return entry.Clone();
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get count HistoryEntries.
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <param name="count"></param>
|
|
/// <param name="newest"></param>
|
|
/// <returns>History entries.</returns>
|
|
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<HistoryInfo>();
|
|
}
|
|
|
|
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<HistoryInfo> entriesList = new List<HistoryInfo>();
|
|
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 entries 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get History Entries based on the WildCard Pattern value.
|
|
/// If passed 0, returns all the values, else return on the basis of count.
|
|
/// </summary>
|
|
/// <param name="wildcardpattern"></param>
|
|
/// <param name="count"></param>
|
|
/// <param name="newest"></param>
|
|
/// <returns></returns>
|
|
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<HistoryInfo> cmdlist = new List<HistoryInfo>();
|
|
long SmallestID = 1;
|
|
// if buffersize is changes,Get the smallest entry that's 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 entry to min entry thats not cleared
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the history entry from buffer for a given id.
|
|
/// </summary>
|
|
/// <param name="id">Id of the entry to be Cleared.</param>
|
|
/// <returns>Nothing.</returns>
|
|
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;
|
|
}
|
|
}
|
|
|
|
///<summary>
|
|
/// gets the total number of entries added
|
|
///</summary>
|
|
///<returns>count of total entries added.</returns>
|
|
|
|
internal int Buffercapacity()
|
|
{
|
|
return _capacity;
|
|
}
|
|
|
|
#endregion internal
|
|
|
|
#region private
|
|
|
|
/// <summary>
|
|
/// Adds an entry to the buffer. If buffer is full, overwrites
|
|
/// oldest entry in the buffer.
|
|
/// </summary>
|
|
/// <param name="entry"></param>
|
|
/// <returns>Returns id for the entry. This id should be used to fetch
|
|
/// the entry from the buffer.</returns>
|
|
/// <remarks>Id starts from 1 and is incremented by 1 for each new entry</remarks>
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets entry from buffer for given id. This id should be the
|
|
/// id returned by Add method.
|
|
/// </summary>
|
|
/// <param name="id">Id of the entry to be fetched.</param>
|
|
/// <returns>Entry corresponding to id if it is present else null
|
|
/// </returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the smallest id in the buffer.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reallocates the buffer if history size changed.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the index for new entry.
|
|
/// </summary>
|
|
/// <returns>Index for new entry.</returns>
|
|
private int GetIndexForNewEntry()
|
|
{
|
|
return (int)(_countEntriesAdded % _capacity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets index in buffer for an entry with given Id.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private int GetIndexFromId(long id)
|
|
{
|
|
return (int)((id - 1) % _capacity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets index in buffer for an entry with given Id using passed in
|
|
/// capacity.
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <param name="capacity"></param>
|
|
/// <returns></returns>
|
|
private static int GetIndexFromId(long id, int capacity)
|
|
{
|
|
return (int)((id - 1) % capacity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Increment number of entries in buffer by 1.
|
|
/// </summary>
|
|
private void IncrementCountOfEntriesInBuffer()
|
|
{
|
|
if (_countEntriesInBuffer < _capacity)
|
|
_countEntriesInBuffer++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current history size.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Buffer.
|
|
/// </summary>
|
|
private HistoryInfo[] _buffer;
|
|
|
|
/// <summary>
|
|
/// Capacity of circular buffer.
|
|
/// </summary>
|
|
private int _capacity;
|
|
|
|
/// <summary>
|
|
/// Number of entries in buffer currently.
|
|
/// </summary>
|
|
private int _countEntriesInBuffer;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private long _countEntriesAdded;
|
|
|
|
/// <summary>
|
|
/// Private object for synchronization.
|
|
/// </summary>
|
|
private object _syncRoot = new object();
|
|
|
|
#endregion private
|
|
|
|
/// <summary>
|
|
/// Return the ID of the next history item to be added.
|
|
/// </summary>
|
|
internal long GetNextHistoryId()
|
|
{
|
|
return _countEntriesAdded + 1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class Implements the get-history command.
|
|
/// </summary>
|
|
[Cmdlet(VerbsCommon.Get, "History", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113317")]
|
|
[OutputType(typeof(HistoryInfo))]
|
|
public class GetHistoryCommand : PSCmdlet
|
|
{
|
|
/// <summary>
|
|
/// Ids of entries to display.
|
|
/// </summary>
|
|
private long[] _id;
|
|
|
|
/// <summary>
|
|
/// Ids of entries to display.
|
|
/// </summary>
|
|
/// <value></value>
|
|
[Parameter(Position = 0, ValueFromPipeline = true)]
|
|
[ValidateRangeAttribute((long)1, long.MaxValue)]
|
|
public long[] Id
|
|
{
|
|
get
|
|
{
|
|
return _id;
|
|
}
|
|
|
|
set
|
|
{
|
|
_id = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is Count parameter specified.
|
|
/// </summary>
|
|
private bool _countParameterSpecified;
|
|
/// <summary>
|
|
/// Count of entries to display. By default, count is the length of the history buffer.
|
|
/// So "Get-History" returns all history entries.
|
|
/// </summary>
|
|
private int _count;
|
|
|
|
/// <summary>
|
|
/// No of History Entries (starting from last) that are to be displayed.
|
|
/// </summary>
|
|
[Parameter(Position = 1)]
|
|
[ValidateRangeAttribute(0, (int)Int16.MaxValue)]
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
return _count;
|
|
}
|
|
|
|
set
|
|
{
|
|
_countParameterSpecified = true;
|
|
_count = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implements the Processing() method for show/History command.
|
|
/// </summary>
|
|
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 default 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]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class implements the Invoke-History command.
|
|
/// </summary>
|
|
[Cmdlet(VerbsLifecycle.Invoke, "History", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113344")]
|
|
public class InvokeHistoryCommand : PSCmdlet
|
|
{
|
|
#region Parameters
|
|
|
|
/// <summary>
|
|
/// Invoke cmd can execute only one history entry. If multiple
|
|
/// ids are provided, we throw error.
|
|
/// </summary>
|
|
private bool _multipleIdProvided;
|
|
private string _id;
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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
|
|
|
|
/// <summary>
|
|
/// Implements the BeginProcessing() method for eval/History command.
|
|
/// </summary>
|
|
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<DataAddedEventArgs> debugAdded = delegate (Object sender, DataAddedEventArgs e) { DebugRecord record = (DebugRecord)((PSDataCollection<DebugRecord>)sender)[e.Index]; WriteDebug(record.Message); };
|
|
EventHandler<DataAddedEventArgs> errorAdded = delegate (Object sender, DataAddedEventArgs e) { ErrorRecord record = (ErrorRecord)((PSDataCollection<ErrorRecord>)sender)[e.Index]; WriteError(record); };
|
|
EventHandler<DataAddedEventArgs> informationAdded = delegate (Object sender, DataAddedEventArgs e) { InformationRecord record = (InformationRecord)((PSDataCollection<InformationRecord>)sender)[e.Index]; WriteInformation(record); };
|
|
EventHandler<DataAddedEventArgs> progressAdded = delegate (Object sender, DataAddedEventArgs e) { ProgressRecord record = (ProgressRecord)((PSDataCollection<ProgressRecord>)sender)[e.Index]; WriteProgress(record); };
|
|
EventHandler<DataAddedEventArgs> verboseAdded = delegate (Object sender, DataAddedEventArgs e) { VerboseRecord record = (VerboseRecord)((PSDataCollection<VerboseRecord>)sender)[e.Index]; WriteVerbose(record.Message); };
|
|
EventHandler<DataAddedEventArgs> warningAdded = delegate (Object sender, DataAddedEventArgs e) { WarningRecord record = (WarningRecord)((PSDataCollection<WarningRecord>)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;
|
|
|
|
LocalRunspace localRunspace = ps.Runspace as LocalRunspace;
|
|
|
|
try
|
|
{
|
|
// Indicate to the system that we are in nested prompt mode, since we are emulating running the command at the prompt.
|
|
// This ensures that the command being run as nested runs in the correct language mode, because CreatePipelineProcessor()
|
|
// always forces CommandOrigin to Internal for nested running commands, and Command.CreateCommandProcessor() forces Internal
|
|
// commands to always run in FullLanguage mode unless in a nested prompt.
|
|
if (localRunspace != null)
|
|
{
|
|
localRunspace.InInternalNestedPrompt = ps.IsNested;
|
|
}
|
|
|
|
Collection<PSObject> results = ps.Invoke();
|
|
if (results.Count > 0)
|
|
{
|
|
WriteObject(results, true);
|
|
}
|
|
|
|
pipeline.RemoveFromInvokeHistoryEntryList(entry);
|
|
}
|
|
finally
|
|
{
|
|
if (localRunspace != null)
|
|
{
|
|
localRunspace.InInternalNestedPrompt = false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper function which gets history entry to invoke.
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Id of history entry to execute.
|
|
/// </summary>
|
|
private long _historyId = -1;
|
|
|
|
/// <summary>
|
|
/// Commandline to execute.
|
|
/// </summary>
|
|
private string _commandLine;
|
|
|
|
/// <summary>
|
|
/// Parse Id parameter to populate _historyId and _commandLine.
|
|
/// </summary>
|
|
private void PopulateIdAndCommandLine()
|
|
{
|
|
if (_id == null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
_historyId = (long)LanguagePrimitives.ConvertTo(_id, typeof(long), System.Globalization.CultureInfo.InvariantCulture);
|
|
}
|
|
catch (PSInvalidCastException)
|
|
{
|
|
_commandLine = _id;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private void ReplaceHistoryString(HistoryInfo entry)
|
|
{
|
|
// Get the current pipeline
|
|
LocalPipeline pipeline = (LocalPipeline)((LocalRunspace)Context.CurrentRunspace).GetCurrentlyRunningPipeline();
|
|
if (pipeline.AddToHistory)
|
|
{
|
|
pipeline.HistoryString = entry.CommandLine;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class Implements the add-history command.
|
|
/// </summary>
|
|
[Cmdlet(VerbsCommon.Add, "History", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113279")]
|
|
[OutputType(typeof(HistoryInfo))]
|
|
public class AddHistoryCommand : PSCmdlet
|
|
{
|
|
#region parameters
|
|
|
|
/// <summary>
|
|
/// This parameter specifies the current pipeline object.
|
|
/// </summary>
|
|
[Parameter(Position = 0, ValueFromPipeline = true)]
|
|
public PSObject[] InputObject { set; get; }
|
|
|
|
private bool _passthru;
|
|
/// <summary>
|
|
/// A Boolean that indicates whether history objects should be
|
|
/// passed to the next element in the pipeline.
|
|
/// </summary>
|
|
[Parameter()]
|
|
public SwitchParameter Passthru
|
|
{
|
|
get { return _passthru; }
|
|
|
|
set { _passthru = value; }
|
|
}
|
|
|
|
#endregion parameters
|
|
|
|
/// <summary>
|
|
/// Override for BeginProcessing.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override for ProcessRecord.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert mshObject that has has the properties of an HistoryInfo
|
|
/// object in to HistoryInfo object.
|
|
/// </summary>
|
|
/// <param name="mshObject">
|
|
/// mshObject to be converted to HistoryInfo.
|
|
/// </param>
|
|
/// <returns>
|
|
/// HistoryInfo object if conversion is successful else null.
|
|
/// </returns>
|
|
#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;
|
|
}
|
|
}
|
|
|
|
///<summary>
|
|
/// This Class implements the Clear History cmdlet
|
|
///</summary>
|
|
|
|
[Cmdlet(VerbsCommon.Clear, "History", SupportsShouldProcess = true, DefaultParameterSetName = "IDParameter", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135199")]
|
|
public class ClearHistoryCommand : PSCmdlet
|
|
{
|
|
#region Command Line Parameters
|
|
|
|
/// <summary>
|
|
/// Specifies the ID of a command in the session history.Clear history clears the entries
|
|
/// wit the specified ID(s)
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Id of a history entry.
|
|
/// </summary>
|
|
|
|
private int[] _id;
|
|
|
|
/// <summary>
|
|
/// Command line name of an entry in the session history.
|
|
/// </summary>
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Commandline parameter.
|
|
/// </summary>
|
|
|
|
private string[] _commandline = null;
|
|
|
|
///<summary>
|
|
/// Clears the specified number of history entries
|
|
///</summary>
|
|
[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
|
|
{
|
|
_countParameterSpecified = true;
|
|
_count = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Count of the history entries.
|
|
/// </summary>
|
|
private int _count = 32;
|
|
|
|
/// <summary>
|
|
/// A boolean variable to indicate if the count parameter specified.
|
|
/// </summary>
|
|
private bool _countParameterSpecified = false;
|
|
|
|
/// <summary>
|
|
/// Specifies whether new entries to be cleared or the default old ones.
|
|
/// </summary>
|
|
|
|
[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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Switch parameter on the history entries.
|
|
/// </summary>
|
|
|
|
private SwitchParameter _newest;
|
|
|
|
#endregion Command Line Parameters
|
|
|
|
/// <summary>
|
|
/// Overriding Begin Processing.
|
|
/// </summary>
|
|
|
|
protected override void BeginProcessing()
|
|
{
|
|
_history = ((LocalRunspace)Context.CurrentRunspace).History;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overriding Process Record.
|
|
/// </summary>
|
|
|
|
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
|
|
|
|
/// <summary>
|
|
/// Clears the session history based on the id parameter
|
|
/// takes no parameters
|
|
/// <returns>Nothing.</returns>
|
|
/// </summary>
|
|
private void ClearHistoryByID()
|
|
{
|
|
if (_countParameterSpecified && 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 (!_countParameterSpecified)
|
|
{
|
|
// 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 (_countParameterSpecified == 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the session history based on the Commandline parameter
|
|
/// takes no parameters
|
|
/// <returns>Nothing.</returns>
|
|
/// </summary>
|
|
private void ClearHistoryByCmdLine()
|
|
{
|
|
// throw an exception for invalid count values
|
|
if (_countParameterSpecified && 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 (!_countParameterSpecified)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the session history based on the input parameter
|
|
/// <param name="id" >Id of the entry to be cleared.</param>
|
|
/// <param name="count" >Count of entries to be cleared.</param>
|
|
/// <param name="cmdline" >Cmdline string to be cleared.</param>
|
|
/// <param name="newest" >Order of the entries.</param>
|
|
/// <returns>Nothing.</returns>
|
|
/// </summary>
|
|
|
|
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 (!_countParameterSpecified && 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);
|
|
}
|
|
|
|
// Clear the History value.
|
|
foreach (HistoryInfo entry in _entries)
|
|
{
|
|
if (entry != null && entry.Cleared == false)
|
|
_history.ClearEntry(entry.Id);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/// <summary>
|
|
/// History obj.
|
|
/// </summary>
|
|
private History _history;
|
|
|
|
/// <summary>
|
|
/// Array of historyinfo objects.
|
|
/// </summary>
|
|
private HistoryInfo[] _entries;
|
|
|
|
#endregion Private
|
|
}
|
|
}
|
|
|