//
// Copyright (C) Microsoft. All rights reserved.
//
#pragma warning disable 1634, 1691
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Management.Automation.Internal;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
#if !CORECLR
// StackFrame and SymbolStore related types are not available in CoreCLR.
using System.Diagnostics.SymbolStore;
using System.Diagnostics;
#endif
namespace System.Management.Automation
{
///
/// Represents the interface to the PowerShell eventing functionality.
/// This class allows you to subscribe to, and receive events.
///
public abstract class PSEventManager
{
/// Sequential event ID
private int _nextEventId = 1;
///
/// Returns a sequential event ID
///
protected int GetNextEventId()
{
return _nextEventId++;
}
///
/// Represents the interface to the PowerShell event queue.
///
public PSEventArgsCollection ReceivedEvents { get; } = new PSEventArgsCollection();
///
/// Gets the list of event subscribers.
///
public abstract List Subscribers { get; }
///
/// Creates a PowerShell event.
///
///
/// An optional identifier that identifies the source event
///
///
/// The object that generated this event
///
///
/// Any event-specific data associated with the event.
///
///
/// Any additional data you wish to attach to the event
///
///
protected abstract PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData);
///
/// Generate a PowerShell event.
///
///
/// An optional identifier that identifies the source event
///
///
/// The object that generated this event
///
///
/// Any event-specific data associated with the event.
///
///
/// Any additional data you wish to attach to the event
///
///
public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData)
{
return this.GenerateEvent(sourceIdentifier, sender, args, extraData, false, false);
}
///
/// Generate a PowerShell event.
///
///
/// An optional identifier that identifies the source event
///
///
/// The object that generated this event
///
///
/// Any event-specific data associated with the event.
///
///
/// Any additional data you wish to attach to the event
///
///
/// True if the event should be triggered in current thread. False for the event
/// to be triggered in a separate thread.
///
///
/// Wait for the event and associated action to be processed and completed.
///
///
public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData,
bool processInCurrentThread, bool waitForCompletionInCurrentThread)
{
PSEventArgs newEvent = CreateEvent(sourceIdentifier, sender, args, extraData);
ProcessNewEvent(newEvent, processInCurrentThread, waitForCompletionInCurrentThread);
return newEvent;
}
///
/// Adds a forwarded event to the current event manager
///
internal abstract void AddForwardedEvent(PSEventArgs forwardedEvent);
///
/// Processes new events (which have either been generated by this instance or forwarded to it)
///
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "InCurrent")]
protected abstract void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread);
///
/// Processes new events (which have either been generated by this instance or forwarded to it)
///
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "InCurrent")]
protected internal virtual void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread,
bool waitForCompletionWhenInCurrentThread)
{
throw new NotImplementedException();
}
///
/// Get the event subscription that corresponds to an identifier
///
///
/// The identifier that identifies the source of the events
///
///
public abstract IEnumerable GetEventSubscribers(string sourceIdentifier);
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent);
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
/// Indicate how many times the subscriber should be triggered before auto-unregister it
/// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount);
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent);
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
/// Indicate how many times the subscriber should be triggered before auto-unregister it
/// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount);
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
/// True, if the handlerDelegate should be processed in the pipeline execution thread (if possible).
///
///
/// Indicate how many times the subscriber should be triggered before auto-unregister it
/// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
/// The default value is zero
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
internal virtual PSEventSubscriber SubscribeEvent(object source,
string eventName,
string sourceIdentifier,
PSObject data,
PSEventReceivedEventHandler handlerDelegate,
bool supportEvent,
bool forwardEvent,
bool shouldQueueAndProcessInExecutionThread,
int maxTriggerCount = 0)
{
return SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount);
}
///
/// Unsubscribes from an event on an object.
///
///
/// The subscriber associated with the event subscription
///
///
public abstract void UnsubscribeEvent(PSEventSubscriber subscriber);
///
/// This event is raised by the event manager to forward events
///
internal abstract event EventHandler ForwardEvent;
}
///
/// Implementation of the PSEventManager for local runspaces.
///
internal class PSLocalEventManager : PSEventManager, IDisposable
{
///
/// Creates a new instance of the PSEventManager class.
///
internal PSLocalEventManager(ExecutionContext context)
{
_eventSubscribers = new Dictionary();
_engineEventSubscribers = new Dictionary>(StringComparer.OrdinalIgnoreCase);
_actionQueue = new Queue();
_context = context;
}
private Dictionary _eventSubscribers;
private Dictionary> _engineEventSubscribers;
private Queue _actionQueue;
private ExecutionContext _context;
private int _nextSubscriptionId = 1;
private double _throttleLimit = 1;
private int _throttleChecks = 0;
// The assembly and module to hold our event registrations
private AssemblyBuilder _eventAssembly = null;
private ModuleBuilder _eventModule = null;
private int _typeId = 0;
#if !CORECLR
private bool debugMode = false;
#endif
///
/// Gets the list of event subscribers.
///
public override List Subscribers
{
get
{
List subscribers = new List();
lock (_eventSubscribers)
{
foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys)
{
subscribers.Add(currentSubscriber);
}
}
return subscribers;
}
}
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public override PSEventSubscriber SubscribeEvent(Object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent)
{
return SubscribeEvent(source, eventName, sourceIdentifier, data, action, supportEvent, forwardEvent, 0);
}
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
/// Indicate how many times the subscriber should be triggered before auto-unregister it
/// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public override PSEventSubscriber SubscribeEvent(Object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount)
{
// Record this subscriber. This may just be a registration for engine events.
PSEventSubscriber subscriber = new PSEventSubscriber(_context, _nextSubscriptionId++, source, eventName, sourceIdentifier, action, supportEvent, forwardEvent, maxTriggerCount);
ProcessNewSubscriber(subscriber, source, eventName, sourceIdentifier, data, supportEvent, forwardEvent);
subscriber.RegisterJob();
return subscriber;
}
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
/// True, if the handlerDelegate should be processed in the pipeline execution thread (if possible).
///
///
/// Indicate how many times the subscriber should be triggered before auto-unregister it
/// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
/// The default value is zero
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
internal override PSEventSubscriber SubscribeEvent(object source,
string eventName,
string sourceIdentifier,
PSObject data,
PSEventReceivedEventHandler handlerDelegate,
bool supportEvent,
bool forwardEvent,
bool shouldQueueAndProcessInExecutionThread,
int maxTriggerCount = 0)
{
PSEventSubscriber newSubscriber = SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount);
newSubscriber.ShouldProcessInExecutionThread = shouldQueueAndProcessInExecutionThread;
return newSubscriber;
}
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public override PSEventSubscriber SubscribeEvent(Object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent)
{
return SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, 0);
}
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
/// Indicate how many times the subscriber should be triggered before auto-unregister it
/// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public override PSEventSubscriber SubscribeEvent(Object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount)
{
// Record this subscriber. This may just be a registration for engine events.
PSEventSubscriber subscriber = new PSEventSubscriber(_context, _nextSubscriptionId++, source, eventName, sourceIdentifier, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount);
ProcessNewSubscriber(subscriber, source, eventName, sourceIdentifier, data, supportEvent, forwardEvent);
subscriber.RegisterJob();
return subscriber;
}
#region OnIdleProcessing
private Timer _timer = null;
private bool _timerInitialized = false;
private bool _isTimerActive = false;
///
///
/// We sample every 100ms to check if the engine is idle (currentlyRunningPipeline == null). If it's "idle"
/// in four consecutive samples, then we believe it's actually idle. In this way we can avoid capturing possible
/// pipeline transitions.
///
private int _consecutiveIdleSamples = 0;
///
///
/// Send on-idle event if the engine is idle. The property "AutoReset" of the timer is always false,
/// so only one handler will be running at anytime. The timer will be enabled again if we can meet
/// the following two conditions.
/// 1. No PowerShell.OnIdle event is sent out
/// 2. A PowerShell.OnIdle event is sent out, and there are still subscribers to the on-idle event
///
private void OnElapsedEvent(object source)
{
var localRunspace = _context.CurrentRunspace as LocalRunspace;
if (localRunspace == null)
{
// This should never happen, the context should always reference to the local runspace
_consecutiveIdleSamples = 0;
return;
}
if (localRunspace.GetCurrentlyRunningPipeline() == null)
{
_consecutiveIdleSamples++;
}
else
{
_consecutiveIdleSamples = 0;
}
if (_consecutiveIdleSamples == 4)
{
_consecutiveIdleSamples = 0;
lock (_engineEventSubscribers)
{
List subscribers = null;
if (_engineEventSubscribers.TryGetValue(PSEngineEvent.OnIdle, out subscribers) && subscribers.Count > 0)
{
// We send out on-idle event and keep enabling the timer only if there still are subscribers to the on-idle event
GenerateEvent(PSEngineEvent.OnIdle, null, new object[] { }, null, false, false);
EnableTimer();
}
else
{
_isTimerActive = false;
}
}
}
else
{
EnableTimer();
}
}
private void InitializeTimer()
{
try
{
_timer = new Timer(OnElapsedEvent, null, Timeout.Infinite, Timeout.Infinite);
}
catch (ObjectDisposedException)
{
// The PSLocalEventManager is disposed, do nothing
}
}
private void EnableTimer()
{
try
{
_timer.Change(100, Timeout.Infinite);
}
catch (ObjectDisposedException)
{
// The PSLocalEventManager is disposed, do nothing
}
}
#endregion OnIdleProcessing
private static Dictionary s_generatedEventHandlers = new Dictionary();
private void ProcessNewSubscriber(PSEventSubscriber subscriber, Object source, string eventName, string sourceIdentifier, PSObject data, bool supportEvent, bool forwardEvent)
{
Delegate handlerDelegate = null;
if (_eventAssembly == null)
{
#if !CORECLR
// Define the assembly that will hold our event handlers
StackFrame callStack = new StackFrame(0, true);
debugMode = (callStack.GetFileName() != null);
#endif
_eventAssembly = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("PSEventHandler"),
AssemblyBuilderAccess.Run);
}
if (_eventModule == null)
{
#if CORECLR
_eventModule = _eventAssembly.DefineDynamicModule("PSGenericEventModule");
#else
_eventModule = _eventAssembly.DefineDynamicModule("PSGenericEventModule", debugMode);
#endif
}
string engineEventSourceIdentifier = null;
bool isOnIdleEvent = false;
// If we are subscribing to an event on an object, generate the supporting delegate
// for that object.
if (source != null)
{
// If the identifier starts with "PowerShell.", then it will collide with engine
// events
if ((sourceIdentifier != null) &&
(sourceIdentifier.StartsWith("PowerShell.", StringComparison.OrdinalIgnoreCase)))
{
string errorMessage = StringUtil.Format(EventingResources.ReservedIdentifier, sourceIdentifier);
throw new ArgumentException(errorMessage, "sourceIdentifier");
}
EventInfo eventInfo = null;
Type sourceType = source as Type ?? source.GetType();
//PowerShell does not support WinRT events.
if (WinRTHelper.IsWinRTType(sourceType))
{
throw new InvalidOperationException(EventingResources.WinRTEventsNotSupported);
}
// Retrieve the event from the object
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase;
eventInfo = sourceType.GetEvent(eventName, bindingFlags);
// If we can't find the event, throw an exception
if (eventInfo == null)
{
string errorMessage = StringUtil.Format(EventingResources.CouldNotFindEvent, eventName);
throw new ArgumentException(errorMessage, "eventName");
}
// Try to set the EnableRaisingEvents property if it defines one
PropertyInfo eventProperty = sourceType.GetProperty("EnableRaisingEvents");
if (eventProperty != null && eventProperty.CanWrite)
{
try
{
object targetObject = eventProperty.SetMethod.IsStatic ? null : source;
eventProperty.SetValue(targetObject, true);
}
catch (TargetInvocationException e)
{
if (e.InnerException != null)
{
throw e.InnerException;
}
else
{
throw;
}
}
}
#if !CORECLR
// If it is a ManagementEventWatcher, enable it
ManagementEventWatcher eventWatcher = source as ManagementEventWatcher;
if (eventWatcher != null)
{
eventWatcher.Start();
}
#endif
// Get its invoke method, and register ourselves as a handler
MethodInfo invokeMethod = eventInfo.EventHandlerType.GetMethod("Invoke");
// We don't support non-void delegates, as the user has no way
// to influence the result. In the .NET Framework, the only
// events that return values are for extremely advanced scenarios:
// System.ResolveEventHandler, and System.Reflection.ModuleResolveEventHandler.
// For these, the Add-Type cmdlet will suffice.
if (invokeMethod.ReturnType != typeof(void))
{
string errorMessage = EventingResources.NonVoidDelegateNotSupported;
throw new ArgumentException(errorMessage, "eventName");
}
// Cache generated event handlers (by type and event name) so that they don't bloat our
// working set by recompiling event handlers for the same event.
string eventHandlerKey = source.GetType().FullName + "|" + eventName;
Type handlerType;
lock (s_generatedEventHandlers)
{
if (!s_generatedEventHandlers.TryGetValue(eventHandlerKey, out handlerType))
{
handlerType = GenerateEventHandler(invokeMethod);
s_generatedEventHandlers[eventHandlerKey] = handlerType;
}
}
// And create an instance of the type
ConstructorInfo constructor =
handlerType.GetConstructor(new Type[] { typeof(PSEventManager), typeof(Object), typeof(string), typeof(PSObject) });
Object handler = constructor.Invoke(new object[] { this, source, sourceIdentifier, data });
MethodInfo eventDelegate = handlerType.GetMethod("EventDelegate", BindingFlags.Public | BindingFlags.Instance);
handlerDelegate = eventDelegate.CreateDelegate(eventInfo.EventHandlerType, handler);
eventInfo.AddEventHandler(source, handlerDelegate);
}
else
{
if (PSEngineEvent.EngineEvents.Contains(sourceIdentifier))
{
engineEventSourceIdentifier = sourceIdentifier;
isOnIdleEvent = String.Equals(engineEventSourceIdentifier, PSEngineEvent.OnIdle, StringComparison.OrdinalIgnoreCase);
}
}
lock (_eventSubscribers)
{
_eventSubscribers[subscriber] = handlerDelegate;
if (engineEventSourceIdentifier == null)
{
return;
}
lock (_engineEventSubscribers)
{
if (isOnIdleEvent && !_timerInitialized)
{
InitializeTimer();
_timerInitialized = true;
}
List subscribers = null;
if (!_engineEventSubscribers.TryGetValue(engineEventSourceIdentifier, out subscribers))
{
subscribers = new List();
_engineEventSubscribers.Add(engineEventSourceIdentifier, subscribers);
}
subscribers.Add(subscriber);
// This subscriber is the only one in the idle event list, we enable the timer
// since the subscriber could be the first one.
if (isOnIdleEvent && !_isTimerActive)
{
EnableTimer();
_isTimerActive = true;
}
}
}
}
///
/// Unsubscribes from an event on an object.
///
///
/// The subscriber associated with the event subscription
///
///
public override void UnsubscribeEvent(PSEventSubscriber subscriber)
{
UnsubscribeEvent(subscriber, false);
}
///
/// Unsubscribes from an event on an object.
///
///
/// The subscriber associated with the event subscription
///
///
/// Indicate if we should skip draining
///
///
private void UnsubscribeEvent(PSEventSubscriber subscriber, bool skipDraining)
{
if (subscriber == null)
{
throw new ArgumentNullException("subscriber");
}
Delegate existingSubscriber = null;
lock (_eventSubscribers)
{
if (subscriber.IsBeingUnsubscribed || !_eventSubscribers.TryGetValue(subscriber, out existingSubscriber))
{
// Already unsubscribed by another thread or the subscriber doesn't exist
return;
}
subscriber.IsBeingUnsubscribed = true;
}
if ((existingSubscriber != null) && (subscriber.SourceObject != null))
{
// Fire the unregistration handler
subscriber.OnPSEventUnsubscribed(subscriber.SourceObject,
new PSEventUnsubscribedEventArgs(subscriber));
EventInfo eventInfo = null;
Type sourceType = subscriber.SourceObject as Type ?? subscriber.SourceObject.GetType();
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase;
eventInfo = sourceType.GetEvent(subscriber.EventName, bindingFlags);
if ((eventInfo != null) && (existingSubscriber != null))
{
eventInfo.RemoveEventHandler(subscriber.SourceObject, existingSubscriber);
}
}
// We don't need to drain pending actions when remove an auto-unregister subscriber
// from ProcessPendingAction method
if (!skipDraining)
{
// Drain any actions pending for this subscriber
DrainPendingActions(subscriber);
}
// Stop the job
if (subscriber.Action != null)
{
subscriber.Action.NotifyJobStopped();
}
lock (_eventSubscribers)
{
_eventSubscribers.Remove(subscriber);
if (PSEngineEvent.EngineEvents.Contains(subscriber.SourceIdentifier))
{
lock (_engineEventSubscribers)
{
_engineEventSubscribers[subscriber.SourceIdentifier].Remove(subscriber);
}
}
}
}
///
/// Creates a PowerShell event.
///
///
/// An optional identifier that identifies the source event
///
///
/// The object that generated this event
///
///
/// Any event-specific data associated with the event.
///
///
/// Any additional data you wish to attach to the event
///
///
protected override PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData)
{
return new PSEventArgs(null, _context.CurrentRunspace.InstanceId, GetNextEventId(), sourceIdentifier, sender, args, extraData);
}
///
/// Adds a forwarded event to the current event manager
///
internal override void AddForwardedEvent(PSEventArgs forwardedEvent)
{
forwardedEvent.EventIdentifier = GetNextEventId();
ProcessNewEvent(forwardedEvent, false);
}
///
/// Processes new events (which have either been generated by this instance or forwarded to it)
///
protected override void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread)
{
ProcessNewEvent(newEvent, processInCurrentThread, false);
}
///
/// Processes new events (which have either been generated by this instance or forwarded to it)
///
protected internal override void ProcessNewEvent(PSEventArgs newEvent,
bool processInCurrentThread,
bool waitForCompletionWhenInCurrentThread)
{
if (processInCurrentThread)
{
ProcessNewEventImplementation(newEvent, true);
ManualResetEventSlim waitHandle = newEvent.EventProcessed;
if (waitHandle != null)
{
// Win8: 738767 In Win7, the processInCurrentThread parameter was used to be
// called processSynchronously. Even though the parameter was called "processSynchronously",
// the event and associated action were not processed synchronously..the event manager
// just added the associated action into event queue. The action can get executed at a later
// time depending on the throttle checks etc. In Win8, to support routing of ScriptBlock
// invocation to the current runspace, we took dependency on eventing infrastructure and
// this required ensuring the event and associated action be processed in the current thread
// synchronously. The below while loop was added for that (win8: 530495). However, fix for
// 530495 resulted in hang for icm | % { icm } case and dynamic event/subscriptions senarios.
// To overcome that, changed "processSynchronously" parameter to "processInCurrentThread" and added
// a new parameter "waitForCompletionWhenInCurrentThread" to trigger blocking for ScriptBlock
// case.
while (waitForCompletionWhenInCurrentThread && !waitHandle.Wait(250))
{
this.ProcessPendingActions();
}
waitHandle.Dispose();
}
}
else
{
ThreadPool.QueueUserWorkItem(new WaitCallback(
delegate (object unused)
{
ProcessNewEventImplementation(newEvent, false);
}));
}
}
///
/// Called from ProcessNewEvent to actually process the event.
///
private void ProcessNewEventImplementation(PSEventArgs newEvent, bool processSynchronously)
{
// Get the subscriber(s) for this event
bool capturedEvent = false;
List actionsHandledInCurrentThread = new List();
List subscribersWithoutActionOrHandler = new List();
foreach (PSEventSubscriber subscriber in GetEventSubscribers(newEvent.SourceIdentifier, true))
{
newEvent.ForwardEvent = subscriber.ForwardEvent;
// If we found a subscriber and it has an action, queue it up
if (subscriber.Action != null)
{
AddAction(new EventAction(subscriber, newEvent), processSynchronously);
capturedEvent = true;
}
else if (subscriber.HandlerDelegate != null)
{
if (subscriber.ShouldProcessInExecutionThread)
{
AddAction(new EventAction(subscriber, newEvent), processSynchronously);
}
else
{
actionsHandledInCurrentThread.Add(subscriber);
}
capturedEvent = true;
}
else
{
subscribersWithoutActionOrHandler.Add(subscriber);
}
}
foreach (PSEventSubscriber subscriber in actionsHandledInCurrentThread)
{
subscriber.HandlerDelegate(newEvent.Sender, newEvent);
AutoUnregisterEventIfNecessary(subscriber);
}
// Otherwise, add it to the queue of unprocessed items, unless we are forwarding the event
if (!capturedEvent)
{
if (newEvent.ForwardEvent)
{
OnForwardEvent(newEvent);
}
else
{
lock (ReceivedEvents.SyncRoot)
{
ReceivedEvents.Add(newEvent);
}
}
foreach (PSEventSubscriber subscriber in subscribersWithoutActionOrHandler)
{
AutoUnregisterEventIfNecessary(subscriber);
}
}
}
// Add an action to the event queue
private void AddAction(EventAction action, bool processSynchronously)
{
if (processSynchronously)
{
// This mutex will get set after the event is processed.
action.Args.EventProcessed = new ManualResetEventSlim();
}
lock (((System.Collections.ICollection)_actionQueue).SyncRoot)
{
// If the engine isn't active, pulse the pipeline.
// When the engine starts up, it will pick up the pending events
_actionQueue.Enqueue(action);
}
PulseEngine();
}
// PowerShell support for async notifications happen through the
// CheckForInterrupts() method on ParseTreeNode. These are only called when
// the engine is active (and processing,) so the Pulse() method
// executes the equivalent of a NOP so that async events
// can be processed when the engine is idle.
private void PulseEngine()
{
try
{
((LocalRunspace)_context.CurrentRunspace).Pulse();
}
catch (ObjectDisposedException)
{ }
}
///
/// Process any pending actions. The parser calls this method before it executes any
/// parse tree node. If we are processing pending actions, we must block the parser.
/// To prevent starvation of the foreground script, we throttle the number of events
/// that we process while the parser is waiting. If the parser is not waiting, we
/// do not throttle the event processing.
///
///
internal void ProcessPendingActions()
{
// We do this check quickly outside of the lock so that the
// main-line scenario is as fast as possible. Also, do the real work
// in a different method so that this method could be inlined.
if (_actionQueue.Count == 0)
return;
ProcessPendingActionsImpl();
}
private void ProcessPendingActionsImpl()
{
// Now, process pending actions
if (IsExecutingEventAction)
return;
try
{
lock (_actionProcessingLock)
{
if (IsExecutingEventAction)
return;
int processed = 0;
_throttleChecks++;
EventAction nextAction;
while ((_throttleLimit * _throttleChecks) >= processed)
{
// Now, check for (and process) pending actions.
// Lock the collection so that it doesn't change from
// beneath us.
lock (((System.Collections.ICollection)_actionQueue).SyncRoot)
{
int queueCount = _actionQueue.Count;
// Exit if the actions have already been processed
if (queueCount == 0)
return;
nextAction = _actionQueue.Dequeue();
}
bool addActionBackToActionQueue = false;
InvokeAction(nextAction, out addActionBackToActionQueue);
processed++;
if (!addActionBackToActionQueue)
{
AutoUnregisterEventIfNecessary(nextAction.Sender);
}
}
if (processed > 0)
_throttleChecks = 0;
}
}
finally
{
if (_actionQueue.Count > 0)
{
// If we still have work remaining, sleep a bit (to give
// other pipelines a chance to interrupt,) and try again.
// This is done on another thread, since we own the runspace lock
// at this point if we're being called from the pipeline
// teardown event. That can result in starvation of
// foreground threads that also want to use the runspace.
ThreadPool.QueueUserWorkItem(new WaitCallback(
delegate (object unused)
{
System.Threading.Thread.Sleep(100);
this.PulseEngine();
}));
}
}
}
///
/// Auto unregister the subscriber if both 'RemainingTriggerCount' and 'RemainingActionsToProcess' become zero
///
private void AutoUnregisterEventIfNecessary(PSEventSubscriber subscriber)
{
bool removeSubscriber = false;
if (subscriber.AutoUnregister)
{
lock (subscriber)
{
subscriber.RemainingActionsToProcess--;
removeSubscriber = subscriber.RemainingTriggerCount == 0 &&
subscriber.RemainingActionsToProcess == 0;
}
}
if (removeSubscriber)
{
UnsubscribeEvent(subscriber, true);
}
}
private object _actionProcessingLock = new Object();
private EventAction _processingAction = null;
///
/// Drain any pending actions for a given subscriber.
/// This is a synchronous (and expensive) operation, but is
/// required so that unregistering for an event / stopping the
/// event job truly discontinues those event arrivals.
///
internal void DrainPendingActions(PSEventSubscriber subscriber)
{
// We do this check quickly outside of the lock so that the
// main-line scenario is as fast as possible.
if (_actionQueue.Count == 0)
return;
// Now, process pending actions
lock (_actionProcessingLock)
{
lock (((System.Collections.ICollection)_actionQueue).SyncRoot)
{
int queueCount = _actionQueue.Count;
// Exit if the actions have already been processed
if (queueCount == 0)
return;
bool needToScanAgain = false;
do
{
EventAction[] pendingActions = _actionQueue.ToArray();
_actionQueue.Clear();
foreach (EventAction pendingAction in pendingActions)
{
// Make sure an event action can unregister itself.
if ((pendingAction.Sender == subscriber) &&
(pendingAction != _processingAction))
{
while (IsExecutingEventAction)
System.Threading.Thread.Sleep(100);
bool addActionBackToActionQueue = false;
InvokeAction(pendingAction, out addActionBackToActionQueue);
if (addActionBackToActionQueue)
{
needToScanAgain = true;
}
}
else
{
_actionQueue.Enqueue(pendingAction);
}
}
} while (needToScanAgain);
}
}
}
private void InvokeAction(EventAction nextAction, out bool addActionBack)
{
lock (_actionProcessingLock)
{
_processingAction = nextAction;
addActionBack = false;
// Invoke the action in its own session state
SessionStateInternal oldSessionState = _context.EngineSessionState;
if (nextAction.Sender.Action != null)
{
_context.EngineSessionState = nextAction.Sender.Action.ScriptBlock.SessionStateInternal;
}
Runspace oldDefault = Runspace.DefaultRunspace;
try
{
// Set the engine's session state to be
// the session state of the action. Also update the default runspace that the
// scriptblock will execute under, so that scriptblocks will stay in the same
// runspace they were invoked from.
Runspace.DefaultRunspace = _context.CurrentRunspace;
if (nextAction.Sender.Action != null)
{
nextAction.Sender.Action.Invoke(nextAction.Sender, nextAction.Args);
}
else
{
nextAction.Sender.HandlerDelegate(nextAction.Sender, nextAction.Args);
}
}
catch (Exception e)
{
// Catch-all OK. This is a third-party call-out.
CommandProcessorBase.CheckForSevereException(e);
if (e is PipelineStoppedException)
{
// Enqueue the action again, as we weren't able to process it.
// It is possible that the PipelineStoppedException gets generated
// to interrupt _our_ event, but is much more likely to get generated
// when somebody wants to interrupt the pipeline that we are interrupting
// (such as a long directory listing.)
AddAction(nextAction, processSynchronously: false);
addActionBack = true;
}
}
finally
{
var eventProcessed = nextAction.Args.EventProcessed;
if (!addActionBack && eventProcessed != null)
{
eventProcessed.Set();
}
Runspace.DefaultRunspace = oldDefault;
_context.EngineSessionState = oldSessionState;
_processingAction = null;
}
}
}
internal bool IsExecutingEventAction
{
get { return (_processingAction != null); }
}
///
/// Get the event subscription that corresponds to an identifier
///
///
/// The identifier that identifies the source of the events
///
///
public override IEnumerable GetEventSubscribers(string sourceIdentifier)
{
return GetEventSubscribers(sourceIdentifier, false);
}
///
/// If we add event filter feature in the future, the filtering work should be done in this method,
/// so that we only return a list of subscribers that will be invoked.
///
private IEnumerable GetEventSubscribers(string sourceIdentifier, bool forNewEventProcessing)
{
List returnedSubscribers = new List();
List subscribersToBeRemoved = new List();
lock (_eventSubscribers)
{
foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys)
{
bool takeActionForEvent = false;
if (String.Equals(currentSubscriber.SourceIdentifier, sourceIdentifier, StringComparison.OrdinalIgnoreCase))
{
if (forNewEventProcessing)
{
// The caller tries to process the event
if (!currentSubscriber.AutoUnregister || currentSubscriber.RemainingTriggerCount > 0)
{
// If we need the event filter feature, it should be added here.
takeActionForEvent = true;
returnedSubscribers.Add(currentSubscriber);
}
}
else
{
// The caller tries to get all subscribers for this event but it's NOT for the event processing purpose
returnedSubscribers.Add(currentSubscriber);
}
// Handle auto-unregister subscribers here
if (forNewEventProcessing && currentSubscriber.AutoUnregister && currentSubscriber.RemainingTriggerCount > 0)
{
lock (currentSubscriber)
{
currentSubscriber.RemainingTriggerCount--;
// For now, 'takeActionForEvent' is always True when we get to this point.
// But once the event filter feature is added, it could be False when we get to this point.
if (takeActionForEvent)
{
currentSubscriber.RemainingActionsToProcess++;
}
// This condition can only happen after the event filter feature is introduced
if (currentSubscriber.RemainingTriggerCount == 0 && currentSubscriber.RemainingActionsToProcess == 0)
{
subscribersToBeRemoved.Add(currentSubscriber);
}
}
}
}
}
}
if (subscribersToBeRemoved.Count > 0)
{
foreach (PSEventSubscriber subscriber in subscribersToBeRemoved)
{
UnsubscribeEvent(subscriber, true);
}
}
return returnedSubscribers;
}
// Generates a type and method to handle a strongly-typed event from an object
// The event handler itself does as little as possible, as dynamically emitted IL
// is error prone and difficult to read. When possible, functionality enhancements
// should go in the PSEventHandler class, from which every event handler type derives.
private Type GenerateEventHandler(MethodInfo invokeSignature)
{
int parameterCount = invokeSignature.GetParameters().Length;
#if !CORECLR
StackFrame callStack = new StackFrame(0, true);
// Get the filename to associate with the debug symbols
ISymbolDocumentWriter doc = null;
if (debugMode)
{
doc = _eventModule.DefineDocument(callStack.GetFileName(), Guid.Empty, Guid.Empty, Guid.Empty);
}
#endif
// Define the type that will respond to the event. It
// derives from PSEventHandler so that complex
// functionality can go into its base class.
TypeBuilder eventType =
_eventModule.DefineType("PSEventHandler_" + _typeId, TypeAttributes.Public, typeof(PSEventHandler));
_typeId++;
// Retrieve the existing constructor
ConstructorInfo existingConstructor =
typeof(PSEventHandler).GetConstructor(
new Type[] { typeof(PSEventManager), typeof(Object), typeof(string), typeof(PSObject) });
#if !CORECLR
if (debugMode)
{
// Mark as debuggable
Type debugAttributeType = typeof(DebuggableAttribute);
ConstructorInfo debugAttributeCtor = debugAttributeType.GetConstructor(new Type[] { typeof(DebuggableAttribute.DebuggingModes) });
CustomAttributeBuilder debugAttributeBuilder = new CustomAttributeBuilder(debugAttributeCtor,
new object[] {
DebuggableAttribute.DebuggingModes.DisableOptimizations |
DebuggableAttribute.DebuggingModes.Default
});
_eventAssembly.SetCustomAttribute(debugAttributeBuilder);
}
#endif
// Define the new constructor
// public TestEventHandler(PSEventManager eventManager, Object sender, string sourceIdentifier, PSObject extraData)
// : base(eventManager, sender, sourceIdentifier, extraData)
ConstructorBuilder eventConstructor =
eventType.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard,
new Type[] { typeof(PSEventManager), typeof(Object), typeof(string), typeof(PSObject) });
ILGenerator extendedConstructor = eventConstructor.GetILGenerator();
extendedConstructor.Emit(OpCodes.Ldarg_0);
extendedConstructor.Emit(OpCodes.Ldarg_1);
extendedConstructor.Emit(OpCodes.Ldarg_2);
extendedConstructor.Emit(OpCodes.Ldarg_3);
extendedConstructor.Emit(OpCodes.Ldarg, 4);
extendedConstructor.Emit(OpCodes.Call, existingConstructor);
extendedConstructor.Emit(OpCodes.Ret);
// Go through each of the parameters in the event signature, and store their types
Type[] parameterTypes = new Type[parameterCount];
int parameterCounter = 0;
foreach (ParameterInfo parameter in invokeSignature.GetParameters())
{
parameterTypes[parameterCounter] = parameter.ParameterType;
parameterCounter++;
}
// public void EventDelegate(Object sender, FileSystemEventArgs e)
MethodBuilder eventMethod = eventType.DefineMethod("EventDelegate",
MethodAttributes.Public, CallingConventions.Standard, invokeSignature.ReturnType, parameterTypes);
// Create new parameters that mimic the parameters of the event method
parameterCounter = 1;
foreach (ParameterInfo parameter in invokeSignature.GetParameters())
{
ParameterBuilder builder = eventMethod.DefineParameter(
parameterCounter, parameter.Attributes, parameter.Name);
parameterCounter++;
}
ILGenerator methodContents = eventMethod.GetILGenerator();
// Object[] args =
LocalBuilder argsBuilder = methodContents.DeclareLocal(typeof(object[]));
#if !CORECLR
if (debugMode)
{
argsBuilder.SetLocalSymInfo("args");
// new Object[ invokeSignature.GetParameters().Length ]
methodContents.MarkSequencePoint(doc, callStack.GetFileLineNumber() - 1, 1, callStack.GetFileLineNumber(), 100);
}
#endif
methodContents.Emit(OpCodes.Ldc_I4, parameterCount);
methodContents.Emit(OpCodes.Newarr, typeof(Object));
// Retrieve the args variable from local variable index 0
methodContents.Emit(OpCodes.Stloc_0);
// Inline, this converts into a series of setting args[n] to
// the argument at the same parameter index
for (int counter = 1; counter <= parameterCount; counter++)
{
#if !CORECLR
if (debugMode)
{
// args[n] = argument[n]
methodContents.MarkSequencePoint(doc, callStack.GetFileLineNumber() - 1, 1, callStack.GetFileLineNumber(), 100);
}
#endif
methodContents.Emit(OpCodes.Ldloc_0);
methodContents.Emit(OpCodes.Ldc_I4, counter - 1);
methodContents.Emit(OpCodes.Ldarg, counter);
// Box the value type if necessary
if (parameterTypes[counter - 1].GetTypeInfo().IsValueType)
{
methodContents.Emit(OpCodes.Box, parameterTypes[counter - 1]);
}
methodContents.Emit(OpCodes.Stelem_Ref);
}
// Gain access to "this"
methodContents.Emit(OpCodes.Ldarg_0);
// Load the "eventManager" private field, and push it onto the stack
FieldInfo eventManagerField = typeof(PSEventHandler).GetField("eventManager",
BindingFlags.NonPublic | BindingFlags.Instance);
methodContents.Emit(OpCodes.Ldfld, eventManagerField);
// Then the "sourceIdentifier" private field
methodContents.Emit(OpCodes.Ldarg_0);
FieldInfo identifierField = typeof(PSEventHandler).GetField("sourceIdentifier",
BindingFlags.NonPublic | BindingFlags.Instance);
methodContents.Emit(OpCodes.Ldfld, identifierField);
// Then the "sender" private field
methodContents.Emit(OpCodes.Ldarg_0);
FieldInfo senderField = typeof(PSEventHandler).GetField("sender",
BindingFlags.NonPublic | BindingFlags.Instance);
methodContents.Emit(OpCodes.Ldfld, senderField);
// Then push the args variable onto the stack
methodContents.Emit(OpCodes.Ldloc_0);
// Then push the "extraData" private field
methodContents.Emit(OpCodes.Ldarg_0);
FieldInfo extraDataField = typeof(PSEventHandler).GetField("extraData",
BindingFlags.NonPublic | BindingFlags.Instance);
methodContents.Emit(OpCodes.Ldfld, extraDataField);
// Finally, invoke the method
MethodInfo generateEventMethod = typeof(PSEventManager).GetMethod(
"GenerateEvent",
new Type[] { typeof(string), typeof(object), typeof(object[]), typeof(PSObject) }
);
#if !CORECLR
if (debugMode)
{
// GenerateEvent(sourceIdentifier, args, extraData);
methodContents.MarkSequencePoint(doc, callStack.GetFileLineNumber() - 1, 1, callStack.GetFileLineNumber(), 100);
}
#endif
methodContents.Emit(OpCodes.Callvirt, generateEventMethod);
// Discard the return value, and return
methodContents.Emit(OpCodes.Pop);
methodContents.Emit(OpCodes.Ret);
return eventType.CreateTypeInfo().AsType();
}
///
/// This event is raised by the event manager to forward events
///
internal override event EventHandler ForwardEvent;
///
/// Raises the ForwardEvent event
///
protected virtual void OnForwardEvent(PSEventArgs e)
{
EventHandler eh = ForwardEvent;
if (eh != null)
{
eh(this, e);
}
}
///
/// Destructor for the EventManager class
///
///
~PSLocalEventManager()
{
Dispose(false);
}
///
/// Disposes the EventManager class.
///
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Stop the timer if it's not null.
/// Unsubscribes from all events.
///
///
/// Whether to actually dispose the object.
///
///
///
public void Dispose(bool disposing)
{
if (disposing)
{
lock (_eventSubscribers)
{
if (_timer != null)
{
_timer.Dispose();
}
foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys.ToArray())
{
UnsubscribeEvent(currentSubscriber);
}
}
}
}
}
///
/// Implementation of PSEventManager for remote runspaces
///
internal class PSRemoteEventManager : PSEventManager
{
/// Computer on which the event was generated
private string _computerName;
/// Runspace on which the event was generated
private Guid _runspaceId;
///
/// Creates an event manager for the given runspace
///
/// Computer on which the event was generated
/// Runspace on which the event was generated
internal PSRemoteEventManager(string computerName, Guid runspaceId)
{
_computerName = computerName;
_runspaceId = runspaceId;
}
///
/// Gets the list of event subscribers.
///
public override List Subscribers
{
get
{
throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
}
}
///
/// Creates a PowerShell event.
///
///
/// An optional identifier that identifies the source event
///
///
/// The object that generated this event
///
///
/// Any event-specific data associated with the event.
///
///
/// Any additional data you wish to attach to the event
///
///
protected override PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData)
{
// note that this is a local call, so we use null for the computer name
return new PSEventArgs(null, _runspaceId, GetNextEventId(), sourceIdentifier, sender, args, extraData);
}
///
/// Adds a forwarded event to the current event manager
///
internal override void AddForwardedEvent(PSEventArgs forwardedEvent)
{
forwardedEvent.EventIdentifier = GetNextEventId();
forwardedEvent.ForwardEvent = false;
// The computer name will be null the first time the event is forwarded; in this case we need to override with the
// remote computer this event manager is associated to. If the event has travelled multiple hops then we do not
// want to override this value since we want to preserve the original computer.
if (forwardedEvent.ComputerName == null || forwardedEvent.ComputerName.Length == 0)
{
forwardedEvent.ComputerName = _computerName;
forwardedEvent.RunspaceId = _runspaceId;
}
ProcessNewEvent(forwardedEvent, false);
}
///
/// Processes new events (which have either been generated by this instance or forwarded to it)
///
protected override void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread)
{
ProcessNewEvent(newEvent, processInCurrentThread, false);
}
///
/// Processes new events (which have either been generated by this instance or forwarded to it)
///
protected internal override void ProcessNewEvent(PSEventArgs newEvent,
bool processInCurrentThread, bool waitForCompletionInCurrentThread)
{
lock (ReceivedEvents.SyncRoot)
{
if (newEvent.ForwardEvent)
{
OnForwardEvent(newEvent);
}
else
{
ReceivedEvents.Add(newEvent);
}
}
}
///
/// Get the event subscription that corresponds to an identifier
///
///
/// The identifier that identifies the source of the events
///
///
public override IEnumerable GetEventSubscribers(string sourceIdentifier)
{
throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
}
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent)
{
throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
}
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
/// Indicate how many times the subscriber should be triggered before auto-unregister it
/// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount)
{
throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
}
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent)
{
throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
}
///
/// Subscribes to an event on an object.
///
///
/// The source object that defines the event
///
///
/// The event to subscribe
///
///
/// An optional subscription identifier to help identify this event subscription
///
///
/// Any additional data you wish to attach to the event
///
///
/// Any action to be invoked when the event arrives
///
///
/// Any action to be invoked when the event arrives
///
///
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
///
///
/// Indicate how many times the subscriber should be triggered before auto-unregister it
/// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
///
///
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount)
{
throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
}
///
/// Unsubscribes from an event on an object.
///
///
/// The subscriber associated with the event subscription
///
///
public override void UnsubscribeEvent(PSEventSubscriber subscriber)
{
throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
}
///
/// This event is raised by the event manager to forward events
///
internal override event EventHandler ForwardEvent;
///
/// Raises the ForwardEvent event
///
protected virtual void OnForwardEvent(PSEventArgs e)
{
EventHandler eh = ForwardEvent;
if (eh != null)
{
eh(this, e);
}
}
}
///
/// Constants that represent PowerShell engine events
///
// Note: If you generate a new engine event that happens frequently,
// (i.e.: variable changes), the user should be required to enable
// that engine event before they make it into the ReceivedEvents channel.
public sealed class PSEngineEvent
{
private PSEngineEvent() { }
///
/// Called when the PowerShell engine is exiting
///
public const string Exiting = "PowerShell.Exiting";
///
/// Call when the PowerShell engine is idle
///
public const string OnIdle = "PowerShell.OnIdle";
///
/// Called when a workflow job is started from a PowerShell script.
///
public const string WorkflowJobStartEvent = "PowerShell.WorkflowJobStartEvent";
///
/// Called during scriptblock invocation
///
internal const string OnScriptBlockInvoke = "PowerShell.OnScriptBlockInvoke";
///
/// Called during scriptblock invocation
///
internal const string GetCommandInfoParameterMetadata = "PowerShell.GetCommandInfoParameterMetadata";
///
/// A HashSet that contains all engine event names
///
internal static readonly HashSet EngineEvents = new HashSet(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnScriptBlockInvoke };
}
///
/// Represents a subscriber to an event
///
public class PSEventSubscriber : IEquatable
{
///
/// Creates an instance of the PSEventSubscriber class for a given source object, event name,
/// and optional source identifier
///
internal PSEventSubscriber(ExecutionContext context, int id, Object source,
string eventName, string sourceIdentifier, bool supportEvent, bool forwardEvent, int maxTriggerCount)
{
_context = context;
SubscriptionId = id;
SourceObject = source;
EventName = eventName;
SourceIdentifier = sourceIdentifier;
SupportEvent = supportEvent;
ForwardEvent = forwardEvent;
IsBeingUnsubscribed = false;
RemainingActionsToProcess = 0;
if (maxTriggerCount <= 0)
{
AutoUnregister = false;
RemainingTriggerCount = -1;
}
else
{
AutoUnregister = true;
RemainingTriggerCount = maxTriggerCount;
}
}
///
/// Creates an instance of the PSEventSubscriber
/// class. Additionally supports an Action scriptblock.
///
internal PSEventSubscriber(ExecutionContext context, int id, Object source,
string eventName, string sourceIdentifier, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount) :
this(context, id, source, eventName, sourceIdentifier, supportEvent, forwardEvent, maxTriggerCount)
{
// Create the bound scriptblock, and job.
if (action != null)
{
ScriptBlock newAction = CreateBoundScriptBlock(action);
Action = new PSEventJob(context.Events, this, newAction, sourceIdentifier);
}
}
internal void RegisterJob()
{
// And this event subscriber to the job repository if it's not a support event.
if (!SupportEvent)
{
if (this.Action != null)
{
JobRepository jobRepository = ((LocalRunspace)_context.CurrentRunspace).JobRepository;
jobRepository.Add(Action);
}
}
}
///
/// Creates an instance of the PSEventSubscriber
/// class. Additionally supports an Action scriptblock.
///
internal PSEventSubscriber(ExecutionContext context, int id, Object source,
string eventName, string sourceIdentifier, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount) :
this(context, id, source, eventName, sourceIdentifier, supportEvent, forwardEvent, maxTriggerCount)
{
HandlerDelegate = handlerDelegate;
}
private ExecutionContext _context;
///
/// Create a bound script block
///
private ScriptBlock CreateBoundScriptBlock(ScriptBlock scriptAction)
{
ScriptBlock newAction = _context.Modules.CreateBoundScriptBlock(_context, scriptAction, true);
// Create a new Error variable so that it doesn't pollute the global errors.
PSVariable errorVariable = new PSVariable("script:Error", new ArrayList(), ScopedItemOptions.Constant);
SessionStateInternal sessionState = newAction.SessionStateInternal;
SessionStateScope scriptScope = sessionState.GetScopeByID("script");
scriptScope.SetVariable(errorVariable.Name, errorVariable, false, true, sessionState, CommandOrigin.Internal);
return newAction;
}
///
/// Get the identifier of this event subscription
///
public int SubscriptionId { get; set; }
///
/// The object to which this event subscription applies
///
public Object SourceObject { get; }
///
/// The event object to which this event subscription applies
///
public string EventName { get; }
///
/// The identifier that identifies the source of these events
///
public string SourceIdentifier { get; }
///
/// The action invoked when this event arrives
///
public PSEventJob Action { get; }
///
/// The delegate invoked when this event arrives
///
public PSEventReceivedEventHandler HandlerDelegate { get; } = null;
///
/// Get the flag that marks this event as a supporting event
///
public bool SupportEvent { get; }
///
/// Gets whether to forward the event to the PowerShell client during a remote execution
///
public bool ForwardEvent { get; }
///
/// Gets whether the event should be processed in the pipeline execution thread.
///
internal bool ShouldProcessInExecutionThread { get; set; }
///
/// Gets whether the event should be unregistered
///
internal bool AutoUnregister { get; private set; }
///
/// Indicate how many new should be added to the action queue.
/// e.g. NumberOfTimesToBeInvoked = 3 means that this subscriber only responses to
/// the first triggered event. So three new actions will be added to the action
/// queue, and the following events will be ignored.
///
internal int RemainingTriggerCount { get; set; }
///
/// Indicate how many actions from this subscriber should be processed.
/// The method "ProcessPendingAction" will unsubscribe this subscriber
/// if it's marked as auto-unregister and this property becomes 0.
///
internal int RemainingActionsToProcess { get; set; }
///
/// Indicate if the subscriber is being subscribed by a thread
///
internal bool IsBeingUnsubscribed { get; set; }
///
/// The event generated when this event subscriber is unregistered
///
public event PSEventUnsubscribedEventHandler Unsubscribed;
#region IComparable Members
///
/// Determines if two PSEventSubscriber instances are equal
///
/// The PSEventSubscriber to which to compare this instance
///
///
public bool Equals(PSEventSubscriber other)
{
if (other == null)
{
return false;
}
return (String.Equals(SubscriptionId, other.SubscriptionId));
}
///
/// Gets the hashcode that represents this PSEventSubscriber instance
///
public override int GetHashCode()
{
return SubscriptionId;
}
#endregion
internal void OnPSEventUnsubscribed(Object sender, PSEventUnsubscribedEventArgs e)
{
if (Unsubscribed != null)
{
Unsubscribed(sender, e);
}
}
}
///
/// The generic event handler from which specific event handlers extend. When possible,
/// add functionality to this class instead of the IL generated by the GenerateEventHandler()
/// method.
///
[SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
public class PSEventHandler
{
///
/// Creates a new instance of the PsEventHandler class
///
public PSEventHandler()
{
}
///
/// Creates a new instance of the PsEventHandler class for a given
/// event manager, source identifier, and extra data
///
///
/// The event manager to which we forward events.
///
///
/// The object that generated this event.
///
///
/// An optional subscription identifier that identifies the
/// source of the event
///
///
/// Any additional data you wish to attach to the event
///
///
public PSEventHandler(PSEventManager eventManager, Object sender, string sourceIdentifier, PSObject extraData)
{
this.eventManager = eventManager;
this.sender = sender;
this.sourceIdentifier = sourceIdentifier;
this.extraData = extraData;
}
///
/// The event manager to which we forward events
///
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
protected PSEventManager eventManager;
///
/// The sender of the event
///
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
protected Object sender;
///
/// An optional identifier that identifies the source of the event
///
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
protected string sourceIdentifier = null;
///
/// Any additional data you wish to attach to the event
///
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
protected PSObject extraData = null;
}
///
/// Event argument associated with the event fired in a remote runspace and forwarded to the current runspace.
///
public class ForwardedEventArgs : EventArgs
{
internal ForwardedEventArgs(PSObject serializedRemoteEventArgs)
{
SerializedRemoteEventArgs = serializedRemoteEventArgs;
}
///
/// Serialized event arguments from the event fired in a remote runspace
///
public PSObject SerializedRemoteEventArgs { get; }
internal static bool IsRemoteSourceEventArgs(object argument)
{
return Deserializer.IsDeserializedInstanceOfType(argument, typeof(EventArgs));
}
}
///
/// PowerShell event arguments
/// This class provides a generic wrapper for event arguments.
///
/// event argument type
internal class PSEventArgs : EventArgs
{
///
/// Event arguments
///
internal T Args;
///
/// Class constructor
///
/// event arguments
public PSEventArgs(T args)
{
Args = args;
}
}
///
/// The event arguments associated with an event
///
public class PSEventArgs : EventArgs
{
///
/// Create a new instance of the PSEventArgs type
///
///
///
/// Computer on which this event was generated
///
///
/// Runspace on which this event was generated
///
///
/// The unique identifier of the event
///
///
/// The source of the event
///
///
/// The object that generated this event
///
///
/// The arguments associated with the handler of the original event.
///
///
/// Additional data attached by the user to this event.
///
internal PSEventArgs(string computerName, Guid runspaceId, int eventIdentifier, string sourceIdentifier, Object sender, Object[] originalArgs, PSObject additionalData)
{
// Capture the first EventArgs as SourceEventArgs
if (originalArgs != null)
{
foreach (Object argument in originalArgs)
{
EventArgs sourceEventArgs = argument as EventArgs;
if (sourceEventArgs != null)
{
SourceEventArgs = sourceEventArgs;
break;
}
if (ForwardedEventArgs.IsRemoteSourceEventArgs(argument))
{
SourceEventArgs = new ForwardedEventArgs((PSObject)argument);
break;
}
}
}
ComputerName = computerName;
RunspaceId = runspaceId;
EventIdentifier = eventIdentifier;
Sender = sender;
SourceArgs = originalArgs;
SourceIdentifier = sourceIdentifier;
TimeGenerated = DateTime.Now;
MessageData = additionalData;
ForwardEvent = false;
}
///
/// Gets the name of the computer on which this event was generated; the value of this property is null for events generated on the local machine.
///
public string ComputerName { get; internal set; }
///
/// Gets the unique identifier of this event
///
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")]
public Guid RunspaceId { get; internal set; }
///
/// Gets the unique identifier of this event
///
public int EventIdentifier { get; internal set; }
///
/// Gets the object that generated this event
///
public Object Sender { get; }
///
/// Gets the first argument from the original event source that
/// derives from EventArgs
///
public EventArgs SourceEventArgs { get; }
///
/// Gets the list of arguments captured by the original event source
///
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public Object[] SourceArgs { get; }
///
/// Gets the identifier associated with the source of this event
///
public String SourceIdentifier { get; }
///
/// Gets the time and date that this event was generated
///
public DateTime TimeGenerated { get; internal set;
// internal setter using during deserialization
}
///
/// Gets the additional user data associated with this event
///
public PSObject MessageData { get; }
///
/// Gets whether to forward the event to the PowerShell client during a remote execution
///
internal bool ForwardEvent { get; set; }
///
/// When processing synchronous events, this mutex is set so we know when we can safely return.
///
internal ManualResetEventSlim EventProcessed { get; set; }
}
///
/// The delegate that handles notifications of new events
/// added to the collection
///
public delegate void PSEventReceivedEventHandler(Object sender, PSEventArgs e);
///
/// The event arguments associated with unsubscribing from an event
///
public class PSEventUnsubscribedEventArgs : EventArgs
{
///
/// Create a new instance of the PSEventUnsubscribedEventArgs type
///
///
///
/// The event subscriber being unregistered
///
internal PSEventUnsubscribedEventArgs(PSEventSubscriber eventSubscriber)
{
EventSubscriber = eventSubscriber;
}
///
/// The event subscriber being unregistered
///
public PSEventSubscriber EventSubscriber { get; internal set; }
}
///
/// The delegate that handles notifications of the event being unsubscribed
///
public delegate void PSEventUnsubscribedEventHandler(Object sender, PSEventUnsubscribedEventArgs e);
///
/// This class contains the collection of events received by the
/// execution context.
///
public class PSEventArgsCollection : IEnumerable
{
///
/// The event generated when a new event is received
///
public event PSEventReceivedEventHandler PSEventReceived;
private List _eventCollection = new List();
///
/// Add add an event to the collection
///
///
///
/// The PSEventArgs instance that represents this event
///
/// Don't add events to the collection directly; use the EventManager instead
internal void Add(PSEventArgs eventToAdd)
{
if (eventToAdd == null)
{
throw new ArgumentNullException("eventToAdd");
}
_eventCollection.Add(eventToAdd);
OnPSEventReceived(eventToAdd.Sender, eventToAdd);
}
///
/// Removes an item at a specific index from the collection
///
public int Count
{
get
{
return _eventCollection.Count;
}
}
///
/// Removes an item at a specific index from the collection
///
public void RemoveAt(int index)
{
_eventCollection.RemoveAt(index);
}
///
/// Gets an item at a specific index from the collection
///
public PSEventArgs this[int index]
{
get
{
return _eventCollection[index];
}
}
private void OnPSEventReceived(Object sender, PSEventArgs e)
{
PSEventReceivedEventHandler eventHandler = PSEventReceived;
if (eventHandler != null)
{
eventHandler(sender, e);
}
}
///
/// Get the enumerator of this collection
///
public IEnumerator GetEnumerator()
{
return _eventCollection.GetEnumerator();
}
///
/// Get the enumerator of this collection
///
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _eventCollection.GetEnumerator();
}
///
/// Get the synchronization root for this collection
///
public object SyncRoot { get; } = new object();
}
///
/// The combination of an event subscriber, and the event that was fired.
/// This is to support the arguments to script blocks that we invoke automatically
/// as a response to some events
///
internal class EventAction
{
public EventAction(PSEventSubscriber sender, PSEventArgs args)
{
Sender = sender;
Args = args;
}
///
/// Get the sender of this event (the event subscriber)
///
public PSEventSubscriber Sender { get; }
///
/// Get the arguments of this event (the event that was fired)
///
public PSEventArgs Args { get; }
}
///
/// A class to give a job-like interface to event actions
///
public class PSEventJob : Job
{
///
/// Creates a new instance of the PSEventJob class.
///
///
/// The event manager that controls the event subscriptions
///
///
/// The subscriber that generates these actions
///
///
/// The script block invoked for this event
///
///
/// The name of the job
///
///
public PSEventJob(PSEventManager eventManager, PSEventSubscriber subscriber, ScriptBlock action, string name) :
base(action == null ? null : action.ToString(), name)
{
if (eventManager == null)
throw new ArgumentNullException("eventManager");
if (subscriber == null)
throw new ArgumentNullException("subscriber");
UsesResultsCollection = true;
ScriptBlock = action;
_eventManager = eventManager;
_subscriber = subscriber;
}
private PSEventManager _eventManager = null;
private PSEventSubscriber _subscriber = null;
private int _highestErrorIndex = 0;
///
/// Gets dynamic module where the action is invoked
///
public PSModuleInfo Module
{
get { return ScriptBlock.Module; }
}
///
/// Stop Job
///
public override void StopJob()
{
_eventManager.UnsubscribeEvent(_subscriber);
}
///
/// Message indicating status of the job
///
public override string StatusMessage { get; } = null;
///
/// indicates if more data is available
///
///
/// This has more data if any of the child jobs have more data.
///
public override bool HasMoreData
{
get
{
return _moreData;
}
}
private bool _moreData = false;
///
/// Location in which this job is running
///
public override string Location
{
get
{
return null;
}
}
///
/// The scriptblock that defines the action
///
internal ScriptBlock ScriptBlock { get; }
///
/// Invoke the script block
///
///
/// The subscriber that generated this event
///
///
/// The context of this event
///
///
internal void Invoke(PSEventSubscriber eventSubscriber, PSEventArgs eventArgs)
{
if (IsFinishedState(JobStateInfo.State))
return;
SetJobState(JobState.Running);
// Prepare the automatic variables
SessionState actionState = ScriptBlock.SessionStateInternal.PublicSessionState;
// $psEventSubscriber = The subscriber
// that generated this event
actionState.PSVariable.Set("eventSubscriber", eventSubscriber);
// $psEvent = The extended event information
actionState.PSVariable.Set("event", eventArgs);
// $sender = $psEvent.Sender
actionState.PSVariable.Set("sender", eventArgs.Sender);
// $eventArgs = $psEvent.SourceEventArgs
actionState.PSVariable.Set("eventArgs", eventArgs.SourceEventArgs);
List