mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
2651 lines
106 KiB
C#
2651 lines
106 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
#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;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
/// <summary>
|
|
/// Represents the interface to the PowerShell eventing functionality.
|
|
/// This class allows you to subscribe to, and receive events.
|
|
/// </summary>
|
|
public abstract class PSEventManager
|
|
{
|
|
/// <summary>Sequential event ID</summary>
|
|
private int _nextEventId = 1;
|
|
|
|
/// <summary>
|
|
/// Returns a sequential event ID
|
|
/// </summary>
|
|
protected int GetNextEventId()
|
|
{
|
|
return _nextEventId++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents the interface to the PowerShell event queue.
|
|
/// </summary>
|
|
public PSEventArgsCollection ReceivedEvents { get; } = new PSEventArgsCollection();
|
|
|
|
/// <summary>
|
|
/// Gets the list of event subscribers.
|
|
/// </summary>
|
|
public abstract List<PSEventSubscriber> Subscribers { get; }
|
|
|
|
/// <summary>
|
|
/// Creates a PowerShell event.
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional identifier that identifies the source event
|
|
/// </param>
|
|
/// <param name="sender">
|
|
/// The object that generated this event
|
|
/// </param>
|
|
/// <param name="args">
|
|
/// Any event-specific data associated with the event.
|
|
/// </param>
|
|
/// <param name="extraData">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// </summary>
|
|
protected abstract PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData);
|
|
|
|
/// <summary>
|
|
/// Generate a PowerShell event.
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional identifier that identifies the source event
|
|
/// </param>
|
|
/// <param name="sender">
|
|
/// The object that generated this event
|
|
/// </param>
|
|
/// <param name="args">
|
|
/// Any event-specific data associated with the event.
|
|
/// </param>
|
|
/// <param name="extraData">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// </summary>
|
|
public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData)
|
|
{
|
|
return this.GenerateEvent(sourceIdentifier, sender, args, extraData, false, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a PowerShell event.
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional identifier that identifies the source event
|
|
/// </param>
|
|
/// <param name="sender">
|
|
/// The object that generated this event
|
|
/// </param>
|
|
/// <param name="args">
|
|
/// Any event-specific data associated with the event.
|
|
/// </param>
|
|
/// <param name="extraData">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="processInCurrentThread">
|
|
/// True if the event should be triggered in current thread. False for the event
|
|
/// to be triggered in a separate thread.
|
|
/// </param>
|
|
/// <param name="waitForCompletionInCurrentThread">
|
|
/// Wait for the event and associated action to be processed and completed.
|
|
/// </param>
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a forwarded event to the current event manager
|
|
/// </summary>
|
|
internal abstract void AddForwardedEvent(PSEventArgs forwardedEvent);
|
|
|
|
/// <summary>
|
|
/// Processes new events (which have either been generated by this instance or forwarded to it)
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "InCurrent")]
|
|
protected abstract void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread);
|
|
|
|
/// <summary>
|
|
/// Processes new events (which have either been generated by this instance or forwarded to it)
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "InCurrent")]
|
|
protected internal virtual void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread,
|
|
bool waitForCompletionWhenInCurrentThread)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the event subscription that corresponds to an identifier
|
|
/// <param name="sourceIdentifier">
|
|
/// The identifier that identifies the source of the events
|
|
/// </param>
|
|
/// </summary>
|
|
public abstract IEnumerable<PSEventSubscriber> GetEventSubscribers(string sourceIdentifier);
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="action">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
|
|
public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent);
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="action">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// <param name="maxTriggerCount">
|
|
/// 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
|
|
/// </param>
|
|
/// </summary>
|
|
[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);
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="handlerDelegate">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
|
|
public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent);
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="handlerDelegate">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// <param name="maxTriggerCount">
|
|
/// 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
|
|
/// </param>
|
|
/// </summary>
|
|
[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);
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="handlerDelegate">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// <param name="shouldQueueAndProcessInExecutionThread">
|
|
/// True, if the handlerDelegate should be processed in the pipeline execution thread (if possible).
|
|
/// </param>
|
|
/// <param name="maxTriggerCount">
|
|
/// 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
|
|
/// </param>
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribes from an event on an object.
|
|
/// <param name="subscriber">
|
|
/// The subscriber associated with the event subscription
|
|
/// </param>
|
|
/// </summary>
|
|
public abstract void UnsubscribeEvent(PSEventSubscriber subscriber);
|
|
|
|
/// <summary>
|
|
/// This event is raised by the event manager to forward events
|
|
/// </summary>
|
|
internal abstract event EventHandler<PSEventArgs> ForwardEvent;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implementation of the PSEventManager for local runspaces.
|
|
/// </summary>
|
|
internal class PSLocalEventManager : PSEventManager, IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Creates a new instance of the PSEventManager class.
|
|
/// </summary>
|
|
internal PSLocalEventManager(ExecutionContext context)
|
|
{
|
|
_eventSubscribers = new Dictionary<PSEventSubscriber, Delegate>();
|
|
_engineEventSubscribers = new Dictionary<string, List<PSEventSubscriber>>(StringComparer.OrdinalIgnoreCase);
|
|
_actionQueue = new Queue<EventAction>();
|
|
_context = context;
|
|
}
|
|
|
|
private Dictionary<PSEventSubscriber, Delegate> _eventSubscribers;
|
|
private Dictionary<string, List<PSEventSubscriber>> _engineEventSubscribers;
|
|
private Queue<EventAction> _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;
|
|
|
|
/// <summary>
|
|
/// Gets the list of event subscribers.
|
|
/// </summary>
|
|
public override List<PSEventSubscriber> Subscribers
|
|
{
|
|
get
|
|
{
|
|
List<PSEventSubscriber> subscribers = new List<PSEventSubscriber>();
|
|
|
|
lock (_eventSubscribers)
|
|
{
|
|
foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys)
|
|
{
|
|
subscribers.Add(currentSubscriber);
|
|
}
|
|
}
|
|
|
|
return subscribers;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="action">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="action">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// <param name="maxTriggerCount">
|
|
/// 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
|
|
/// </param>
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="handlerDelegate">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// <param name="shouldQueueAndProcessInExecutionThread">
|
|
/// True, if the handlerDelegate should be processed in the pipeline execution thread (if possible).
|
|
/// </param>
|
|
/// <param name="maxTriggerCount">
|
|
/// 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
|
|
/// </param>
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="handlerDelegate">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="handlerDelegate">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// <param name="maxTriggerCount">
|
|
/// 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
|
|
/// </param>
|
|
/// </summary>
|
|
[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;
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private int _consecutiveIdleSamples = 0;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
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<PSEventSubscriber> 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<string, Type> s_generatedEventHandlers = new Dictionary<string, Type>();
|
|
private void ProcessNewSubscriber(PSEventSubscriber subscriber, object source, string eventName, string sourceIdentifier, PSObject data, bool supportEvent, bool forwardEvent)
|
|
{
|
|
Delegate handlerDelegate = null;
|
|
|
|
if (_eventAssembly == null)
|
|
{
|
|
_eventAssembly = AssemblyBuilder.DefineDynamicAssembly(
|
|
new AssemblyName("PSEventHandler"),
|
|
AssemblyBuilderAccess.Run);
|
|
_eventModule = _eventAssembly.DefineDynamicModule("PSGenericEventModule");
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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<PSEventSubscriber> subscribers = null;
|
|
if (!_engineEventSubscribers.TryGetValue(engineEventSourceIdentifier, out subscribers))
|
|
{
|
|
subscribers = new List<PSEventSubscriber>();
|
|
_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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribes from an event on an object.
|
|
/// <param name="subscriber">
|
|
/// The subscriber associated with the event subscription
|
|
/// </param>
|
|
/// </summary>
|
|
public override void UnsubscribeEvent(PSEventSubscriber subscriber)
|
|
{
|
|
UnsubscribeEvent(subscriber, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribes from an event on an object.
|
|
/// <param name="subscriber">
|
|
/// The subscriber associated with the event subscription
|
|
/// </param>
|
|
/// <param name="skipDraining">
|
|
/// Indicate if we should skip draining
|
|
/// </param>
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a PowerShell event.
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional identifier that identifies the source event
|
|
/// </param>
|
|
/// <param name="sender">
|
|
/// The object that generated this event
|
|
/// </param>
|
|
/// <param name="args">
|
|
/// Any event-specific data associated with the event.
|
|
/// </param>
|
|
/// <param name="extraData">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// </summary>
|
|
protected override PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData)
|
|
{
|
|
return new PSEventArgs(null, _context.CurrentRunspace.InstanceId, GetNextEventId(), sourceIdentifier, sender, args, extraData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a forwarded event to the current event manager
|
|
/// </summary>
|
|
internal override void AddForwardedEvent(PSEventArgs forwardedEvent)
|
|
{
|
|
forwardedEvent.EventIdentifier = GetNextEventId();
|
|
|
|
ProcessNewEvent(forwardedEvent, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes new events (which have either been generated by this instance or forwarded to it)
|
|
/// </summary>
|
|
protected override void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread)
|
|
{
|
|
ProcessNewEvent(newEvent, processInCurrentThread, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes new events (which have either been generated by this instance or forwarded to it)
|
|
/// </summary>
|
|
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 not responding for icm | % { icm } case and dynamic event/subscriptions scenarios.
|
|
// 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);
|
|
}));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called from ProcessNewEvent to actually process the event.
|
|
/// </summary>
|
|
private void ProcessNewEventImplementation(PSEventArgs newEvent, bool processSynchronously)
|
|
{
|
|
// Get the subscriber(s) for this event
|
|
bool capturedEvent = false;
|
|
List<PSEventSubscriber> actionsHandledInCurrentThread = new List<PSEventSubscriber>();
|
|
List<PSEventSubscriber> subscribersWithoutActionOrHandler = new List<PSEventSubscriber>();
|
|
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)
|
|
{ }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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();
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Auto unregister the subscriber if both 'RemainingTriggerCount' and 'RemainingActionsToProcess' become zero
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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.
|
|
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); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the event subscription that corresponds to an identifier
|
|
/// <param name="sourceIdentifier">
|
|
/// The identifier that identifies the source of the events
|
|
/// </param>
|
|
/// </summary>
|
|
public override IEnumerable<PSEventSubscriber> GetEventSubscribers(string sourceIdentifier)
|
|
{
|
|
return GetEventSubscribers(sourceIdentifier, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private IEnumerable<PSEventSubscriber> GetEventSubscribers(string sourceIdentifier, bool forNewEventProcessing)
|
|
{
|
|
List<PSEventSubscriber> returnedSubscribers = new List<PSEventSubscriber>();
|
|
List<PSEventSubscriber> subscribersToBeRemoved = new List<PSEventSubscriber>();
|
|
|
|
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;
|
|
|
|
// 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) });
|
|
|
|
// 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();
|
|
|
|
// Declare a local variable of the type 'object[]' at index 0, say 'object[] args'
|
|
methodContents.DeclareLocal(typeof(object[]));
|
|
|
|
methodContents.Emit(OpCodes.Ldc_I4, parameterCount);
|
|
methodContents.Emit(OpCodes.Newarr, typeof(Object));
|
|
|
|
// Store the new array to the local variable 'args'
|
|
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++)
|
|
{
|
|
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].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(
|
|
nameof(PSEventManager.GenerateEvent),
|
|
new Type[] { typeof(string), typeof(object), typeof(object[]), typeof(PSObject) });
|
|
|
|
methodContents.Emit(OpCodes.Callvirt, generateEventMethod);
|
|
|
|
// Discard the return value, and return
|
|
methodContents.Emit(OpCodes.Pop);
|
|
methodContents.Emit(OpCodes.Ret);
|
|
|
|
return eventType.CreateTypeInfo().AsType();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This event is raised by the event manager to forward events
|
|
/// </summary>
|
|
internal override event EventHandler<PSEventArgs> ForwardEvent;
|
|
|
|
/// <summary>
|
|
/// Raises the ForwardEvent event
|
|
/// </summary>
|
|
protected virtual void OnForwardEvent(PSEventArgs e)
|
|
{
|
|
EventHandler<PSEventArgs> eh = ForwardEvent;
|
|
|
|
if (eh != null)
|
|
{
|
|
eh(this, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destructor for the EventManager class
|
|
/// </summary>
|
|
~PSLocalEventManager()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disposes the EventManager class.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop the timer if it's not null.
|
|
/// Unsubscribes from all events.
|
|
/// <param name="disposing">
|
|
/// Whether to actually dispose the object.
|
|
/// </param>
|
|
/// </summary>
|
|
public void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
lock (_eventSubscribers)
|
|
{
|
|
if (_timer != null)
|
|
{
|
|
_timer.Dispose();
|
|
}
|
|
|
|
foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys.ToArray())
|
|
{
|
|
UnsubscribeEvent(currentSubscriber);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implementation of PSEventManager for remote runspaces
|
|
/// </summary>
|
|
internal class PSRemoteEventManager : PSEventManager
|
|
{
|
|
/// <summary>Computer on which the event was generated</summary>
|
|
private string _computerName;
|
|
|
|
/// <summary>Runspace on which the event was generated</summary>
|
|
private Guid _runspaceId;
|
|
|
|
/// <summary>
|
|
/// Creates an event manager for the given runspace
|
|
/// </summary>
|
|
/// <param name="computerName">Computer on which the event was generated</param>
|
|
/// <param name="runspaceId">Runspace on which the event was generated</param>
|
|
internal PSRemoteEventManager(string computerName, Guid runspaceId)
|
|
{
|
|
_computerName = computerName;
|
|
_runspaceId = runspaceId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the list of event subscribers.
|
|
/// </summary>
|
|
public override List<PSEventSubscriber> Subscribers
|
|
{
|
|
get
|
|
{
|
|
throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a PowerShell event.
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional identifier that identifies the source event
|
|
/// </param>
|
|
/// <param name="sender">
|
|
/// The object that generated this event
|
|
/// </param>
|
|
/// <param name="args">
|
|
/// Any event-specific data associated with the event.
|
|
/// </param>
|
|
/// <param name="extraData">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a forwarded event to the current event manager
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes new events (which have either been generated by this instance or forwarded to it)
|
|
/// </summary>
|
|
protected override void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread)
|
|
{
|
|
ProcessNewEvent(newEvent, processInCurrentThread, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes new events (which have either been generated by this instance or forwarded to it)
|
|
/// </summary>
|
|
protected internal override void ProcessNewEvent(PSEventArgs newEvent,
|
|
bool processInCurrentThread, bool waitForCompletionInCurrentThread)
|
|
{
|
|
lock (ReceivedEvents.SyncRoot)
|
|
{
|
|
if (newEvent.ForwardEvent)
|
|
{
|
|
OnForwardEvent(newEvent);
|
|
}
|
|
else
|
|
{
|
|
ReceivedEvents.Add(newEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the event subscription that corresponds to an identifier
|
|
/// <param name="sourceIdentifier">
|
|
/// The identifier that identifies the source of the events
|
|
/// </param>
|
|
/// </summary>
|
|
public override IEnumerable<PSEventSubscriber> GetEventSubscribers(string sourceIdentifier)
|
|
{
|
|
throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="action">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="action">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// <param name="maxTriggerCount">
|
|
/// 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
|
|
/// </param>
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="handlerDelegate">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to an event on an object.
|
|
/// <param name="source">
|
|
/// The source object that defines the event
|
|
/// </param>
|
|
/// <param name="eventName">
|
|
/// The event to subscribe
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier to help identify this event subscription
|
|
/// </param>
|
|
/// <param name="data">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// <param name="handlerDelegate">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="supportEvent">
|
|
/// Any action to be invoked when the event arrives
|
|
/// </param>
|
|
/// <param name="forwardEvent">
|
|
/// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
|
|
/// </param>
|
|
/// <param name="maxTriggerCount">
|
|
/// 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
|
|
/// </param>
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribes from an event on an object.
|
|
/// <param name="subscriber">
|
|
/// The subscriber associated with the event subscription
|
|
/// </param>
|
|
/// </summary>
|
|
public override void UnsubscribeEvent(PSEventSubscriber subscriber)
|
|
{
|
|
throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This event is raised by the event manager to forward events
|
|
/// </summary>
|
|
internal override event EventHandler<PSEventArgs> ForwardEvent;
|
|
|
|
/// <summary>
|
|
/// Raises the ForwardEvent event
|
|
/// </summary>
|
|
protected virtual void OnForwardEvent(PSEventArgs e)
|
|
{
|
|
EventHandler<PSEventArgs> eh = ForwardEvent;
|
|
|
|
if (eh != null)
|
|
{
|
|
eh(this, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constants that represent PowerShell engine events
|
|
/// </summary>
|
|
// 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() { }
|
|
|
|
/// <summary>
|
|
/// Called when the PowerShell engine is exiting
|
|
/// </summary>
|
|
public const string Exiting = "PowerShell.Exiting";
|
|
|
|
/// <summary>
|
|
/// Call when the PowerShell engine is idle
|
|
/// </summary>
|
|
public const string OnIdle = "PowerShell.OnIdle";
|
|
|
|
/// <summary>
|
|
/// Called when a workflow job is started from a PowerShell script.
|
|
/// </summary>
|
|
public const string WorkflowJobStartEvent = "PowerShell.WorkflowJobStartEvent";
|
|
|
|
/// <summary>
|
|
/// Called during scriptblock invocation
|
|
/// </summary>
|
|
internal const string OnScriptBlockInvoke = "PowerShell.OnScriptBlockInvoke";
|
|
|
|
/// <summary>
|
|
/// Called during scriptblock invocation
|
|
/// </summary>
|
|
internal const string GetCommandInfoParameterMetadata = "PowerShell.GetCommandInfoParameterMetadata";
|
|
|
|
/// <summary>
|
|
/// A HashSet that contains all engine event names
|
|
/// </summary>
|
|
internal static readonly HashSet<string> EngineEvents = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnScriptBlockInvoke };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a subscriber to an event
|
|
/// </summary>
|
|
public class PSEventSubscriber : IEquatable<PSEventSubscriber>
|
|
{
|
|
/// <summary>
|
|
/// Creates an instance of the PSEventSubscriber class for a given source object, event name,
|
|
/// and optional source identifier
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an instance of the PSEventSubscriber
|
|
/// class. Additionally supports an Action scriptblock.
|
|
/// </summary>
|
|
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()
|
|
{
|
|
// Add 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an instance of the PSEventSubscriber
|
|
/// class. Additionally supports an Action scriptblock.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Create a bound script block
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the identifier of this event subscription
|
|
/// </summary>
|
|
public int SubscriptionId { get; set; }
|
|
|
|
/// <summary>
|
|
/// The object to which this event subscription applies
|
|
/// </summary>
|
|
public object SourceObject { get; }
|
|
|
|
/// <summary>
|
|
/// The event object to which this event subscription applies
|
|
/// </summary>
|
|
public string EventName { get; }
|
|
|
|
/// <summary>
|
|
/// The identifier that identifies the source of these events
|
|
/// </summary>
|
|
public string SourceIdentifier { get; }
|
|
|
|
/// <summary>
|
|
/// The action invoked when this event arrives
|
|
/// </summary>
|
|
public PSEventJob Action { get; }
|
|
|
|
/// <summary>
|
|
/// The delegate invoked when this event arrives
|
|
/// </summary>
|
|
public PSEventReceivedEventHandler HandlerDelegate { get; } = null;
|
|
|
|
/// <summary>
|
|
/// Get the flag that marks this event as a supporting event
|
|
/// </summary>
|
|
public bool SupportEvent { get; }
|
|
|
|
/// <summary>
|
|
/// Gets whether to forward the event to the PowerShell client during a remote execution
|
|
/// </summary>
|
|
public bool ForwardEvent { get; }
|
|
|
|
/// <summary>
|
|
/// Gets whether the event should be processed in the pipeline execution thread.
|
|
/// </summary>
|
|
internal bool ShouldProcessInExecutionThread { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets whether the event should be unregistered
|
|
/// </summary>
|
|
internal bool AutoUnregister { get; private set; }
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
internal int RemainingTriggerCount { get; set; }
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
internal int RemainingActionsToProcess { get; set; }
|
|
|
|
/// <summary>
|
|
/// Indicate if the subscriber is being subscribed by a thread
|
|
/// </summary>
|
|
internal bool IsBeingUnsubscribed { get; set; }
|
|
|
|
/// <summary>
|
|
/// The event generated when this event subscriber is unregistered
|
|
/// </summary>
|
|
public event PSEventUnsubscribedEventHandler Unsubscribed;
|
|
|
|
#region IComparable<PSEventSubscriber> Members
|
|
|
|
/// <summary>
|
|
/// Determines if two PSEventSubscriber instances are equal
|
|
/// <param name="other">
|
|
/// The PSEventSubscriber to which to compare this instance
|
|
/// </param>
|
|
/// </summary>
|
|
public bool Equals(PSEventSubscriber other)
|
|
{
|
|
if (other == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (String.Equals(SubscriptionId, other.SubscriptionId));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the hashcode that represents this PSEventSubscriber instance
|
|
/// </summary>
|
|
public override int GetHashCode()
|
|
{
|
|
return SubscriptionId;
|
|
}
|
|
#endregion
|
|
|
|
internal void OnPSEventUnsubscribed(Object sender, PSEventUnsubscribedEventArgs e)
|
|
{
|
|
if (Unsubscribed != null)
|
|
{
|
|
Unsubscribed(sender, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
|
|
public class PSEventHandler
|
|
{
|
|
/// <summary>
|
|
/// Creates a new instance of the PsEventHandler class
|
|
/// </summary>
|
|
public PSEventHandler()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the PsEventHandler class for a given
|
|
/// event manager, source identifier, and extra data
|
|
/// <param name="eventManager">
|
|
/// The event manager to which we forward events.
|
|
/// </param>
|
|
/// <param name="sender">
|
|
/// The object that generated this event.
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// An optional subscription identifier that identifies the
|
|
/// source of the event
|
|
/// </param>
|
|
/// <param name="extraData">
|
|
/// Any additional data you wish to attach to the event
|
|
/// </param>
|
|
/// </summary>
|
|
public PSEventHandler(PSEventManager eventManager, object sender, string sourceIdentifier, PSObject extraData)
|
|
{
|
|
this.eventManager = eventManager;
|
|
this.sender = sender;
|
|
this.sourceIdentifier = sourceIdentifier;
|
|
this.extraData = extraData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The event manager to which we forward events
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
|
|
protected PSEventManager eventManager;
|
|
|
|
/// <summary>
|
|
/// The sender of the event
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
|
|
protected object sender;
|
|
|
|
/// <summary>
|
|
/// An optional identifier that identifies the source of the event
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
|
|
protected string sourceIdentifier = null;
|
|
|
|
/// <summary>
|
|
/// Any additional data you wish to attach to the event
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
|
|
protected PSObject extraData = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event argument associated with the event fired in a remote runspace and forwarded to the current runspace.
|
|
/// </summary>
|
|
public class ForwardedEventArgs : EventArgs
|
|
{
|
|
internal ForwardedEventArgs(PSObject serializedRemoteEventArgs)
|
|
{
|
|
SerializedRemoteEventArgs = serializedRemoteEventArgs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialized event arguments from the event fired in a remote runspace
|
|
/// </summary>
|
|
public PSObject SerializedRemoteEventArgs { get; }
|
|
|
|
internal static bool IsRemoteSourceEventArgs(object argument)
|
|
{
|
|
return Deserializer.IsDeserializedInstanceOfType(argument, typeof(EventArgs));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// PowerShell event arguments
|
|
/// This class provides a generic wrapper for event arguments.
|
|
/// </summary>
|
|
/// <typeparam name="T">event argument type</typeparam>
|
|
internal class PSEventArgs<T> : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// Event arguments
|
|
/// </summary>
|
|
internal T Args;
|
|
|
|
/// <summary>
|
|
/// Class constructor
|
|
/// </summary>
|
|
/// <param name="args">event arguments</param>
|
|
public PSEventArgs(T args)
|
|
{
|
|
Args = args;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The event arguments associated with an event
|
|
/// </summary>
|
|
public class PSEventArgs : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// Create a new instance of the PSEventArgs type
|
|
/// </summary>
|
|
/// <param name="computerName">
|
|
/// Computer on which this event was generated
|
|
/// </param>
|
|
/// <param name="runspaceId">
|
|
/// Runspace on which this event was generated
|
|
/// </param>
|
|
/// <param name="eventIdentifier">
|
|
/// The unique identifier of the event
|
|
/// </param>
|
|
/// <param name="sourceIdentifier">
|
|
/// The source of the event
|
|
/// </param>
|
|
/// <param name="sender">
|
|
/// The object that generated this event
|
|
/// </param>
|
|
/// <param name="originalArgs">
|
|
/// The arguments associated with the handler of the original event.
|
|
/// </param>
|
|
/// <param name="additionalData">
|
|
/// Additional data attached by the user to this event.
|
|
/// </param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public string ComputerName { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Gets the unique identifier of this event
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")]
|
|
public Guid RunspaceId { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Gets the unique identifier of this event
|
|
/// </summary>
|
|
public int EventIdentifier { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Gets the object that generated this event
|
|
/// </summary>
|
|
public object Sender { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the first argument from the original event source that
|
|
/// derives from EventArgs
|
|
/// </summary>
|
|
public EventArgs SourceEventArgs { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the list of arguments captured by the original event source
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
|
public object[] SourceArgs { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the identifier associated with the source of this event
|
|
/// </summary>
|
|
public string SourceIdentifier { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the time and date that this event was generated
|
|
/// </summary>
|
|
public DateTime TimeGenerated { get; internal set;
|
|
// internal setter using during deserialization
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the additional user data associated with this event
|
|
/// </summary>
|
|
public PSObject MessageData { get; }
|
|
|
|
/// <summary>
|
|
/// Gets whether to forward the event to the PowerShell client during a remote execution
|
|
/// </summary>
|
|
internal bool ForwardEvent { get; set; }
|
|
|
|
/// <summary>
|
|
/// When processing synchronous events, this mutex is set so we know when we can safely return.
|
|
/// </summary>
|
|
internal ManualResetEventSlim EventProcessed { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The delegate that handles notifications of new events
|
|
/// added to the collection
|
|
/// </summary>
|
|
public delegate void PSEventReceivedEventHandler(Object sender, PSEventArgs e);
|
|
|
|
/// <summary>
|
|
/// The event arguments associated with unsubscribing from an event
|
|
/// </summary>
|
|
public class PSEventUnsubscribedEventArgs : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// Create a new instance of the PSEventUnsubscribedEventArgs type
|
|
/// </summary>
|
|
/// <param name="eventSubscriber">
|
|
/// The event subscriber being unregistered
|
|
/// </param>
|
|
internal PSEventUnsubscribedEventArgs(PSEventSubscriber eventSubscriber)
|
|
{
|
|
EventSubscriber = eventSubscriber;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The event subscriber being unregistered
|
|
/// </summary>
|
|
public PSEventSubscriber EventSubscriber { get; internal set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The delegate that handles notifications of the event being unsubscribed
|
|
/// </summary>
|
|
public delegate void PSEventUnsubscribedEventHandler(Object sender, PSEventUnsubscribedEventArgs e);
|
|
|
|
/// <summary>
|
|
/// This class contains the collection of events received by the
|
|
/// execution context.
|
|
/// </summary>
|
|
public class PSEventArgsCollection : IEnumerable<PSEventArgs>
|
|
{
|
|
/// <summary>
|
|
/// The event generated when a new event is received
|
|
/// </summary>
|
|
public event PSEventReceivedEventHandler PSEventReceived;
|
|
private List<PSEventArgs> _eventCollection = new List<PSEventArgs>();
|
|
|
|
/// <summary>
|
|
/// Add add an event to the collection
|
|
/// </summary>
|
|
/// <param name="eventToAdd">
|
|
/// The PSEventArgs instance that represents this event
|
|
/// </param>
|
|
/// <remarks>Don't add events to the collection directly; use the EventManager instead</remarks>
|
|
internal void Add(PSEventArgs eventToAdd)
|
|
{
|
|
if (eventToAdd == null)
|
|
{
|
|
throw new ArgumentNullException("eventToAdd");
|
|
}
|
|
|
|
_eventCollection.Add(eventToAdd);
|
|
|
|
OnPSEventReceived(eventToAdd.Sender, eventToAdd);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an item at a specific index from the collection
|
|
/// </summary>
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
return _eventCollection.Count;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an item at a specific index from the collection
|
|
/// </summary>
|
|
public void RemoveAt(int index)
|
|
{
|
|
_eventCollection.RemoveAt(index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an item at a specific index from the collection
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the enumerator of this collection
|
|
/// </summary>
|
|
public IEnumerator<PSEventArgs> GetEnumerator()
|
|
{
|
|
return _eventCollection.GetEnumerator();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the enumerator of this collection
|
|
/// </summary>
|
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
|
{
|
|
return _eventCollection.GetEnumerator();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the synchronization root for this collection
|
|
/// </summary>
|
|
public object SyncRoot { get; } = new object();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
internal class EventAction
|
|
{
|
|
public EventAction(PSEventSubscriber sender, PSEventArgs args)
|
|
{
|
|
Sender = sender;
|
|
Args = args;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the sender of this event (the event subscriber)
|
|
/// </summary>
|
|
public PSEventSubscriber Sender { get; }
|
|
|
|
/// <summary>
|
|
/// Get the arguments of this event (the event that was fired)
|
|
/// </summary>
|
|
public PSEventArgs Args { get; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// A class to give a job-like interface to event actions
|
|
/// </summary>
|
|
public class PSEventJob : Job
|
|
{
|
|
/// <summary>
|
|
/// Creates a new instance of the PSEventJob class.
|
|
/// <param name="eventManager">
|
|
/// The event manager that controls the event subscriptions
|
|
/// </param>
|
|
/// <param name="subscriber">
|
|
/// The subscriber that generates these actions
|
|
/// </param>
|
|
/// <param name="action">
|
|
/// The script block invoked for this event
|
|
/// </param>
|
|
/// <param name="name">
|
|
/// The name of the job
|
|
/// </param>
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Gets dynamic module where the action is invoked
|
|
/// </summary>
|
|
public PSModuleInfo Module
|
|
{
|
|
get { return ScriptBlock.Module; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop Job
|
|
/// </summary>
|
|
public override void StopJob()
|
|
{
|
|
_eventManager.UnsubscribeEvent(_subscriber);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Message indicating status of the job
|
|
/// </summary>
|
|
public override string StatusMessage { get; } = null;
|
|
|
|
/// <summary>
|
|
/// indicates if more data is available
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This has more data if any of the child jobs have more data.
|
|
/// </remarks>
|
|
public override bool HasMoreData
|
|
{
|
|
get
|
|
{
|
|
return _moreData;
|
|
}
|
|
}
|
|
|
|
private bool _moreData = false;
|
|
|
|
/// <summary>
|
|
/// Location in which this job is running
|
|
/// </summary>
|
|
public override string Location
|
|
{
|
|
get
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The scriptblock that defines the action
|
|
/// </summary>
|
|
internal ScriptBlock ScriptBlock { get; }
|
|
|
|
/// <summary>
|
|
/// Invoke the script block
|
|
/// <param name="eventSubscriber">
|
|
/// The subscriber that generated this event
|
|
/// </param>
|
|
/// <param name="eventArgs">
|
|
/// The context of this event
|
|
/// </param>
|
|
/// </summary>
|
|
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<object> results = new List<object>();
|
|
|
|
// $args = $psEventArgs.SourceArgs (for PARAM statement)
|
|
try
|
|
{
|
|
Pipe outputPipe = new Pipe(results);
|
|
ScriptBlock.InvokeWithPipe(
|
|
useLocalScope: false,
|
|
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe,
|
|
dollarUnder: AutomationNull.Value,
|
|
input: AutomationNull.Value,
|
|
scriptThis: AutomationNull.Value,
|
|
outputPipe: outputPipe,
|
|
invocationInfo: null,
|
|
args: eventArgs.SourceArgs);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Catch-all OK. This is a third-party call-out.
|
|
if (!(e is PipelineStoppedException))
|
|
{
|
|
LogErrorsAndOutput(results, actionState);
|
|
SetJobState(JobState.Failed);
|
|
}
|
|
|
|
throw;
|
|
}
|
|
/*
|
|
* Not clearing the variables because of Win8: 738767 (Event actions no longer have persistent event variables)
|
|
*/
|
|
|
|
LogErrorsAndOutput(results, actionState);
|
|
_moreData = true;
|
|
}
|
|
|
|
internal void NotifyJobStopped()
|
|
{
|
|
SetJobState(JobState.Stopped);
|
|
_moreData = false;
|
|
}
|
|
|
|
private void LogErrorsAndOutput(List<object> results, SessionState actionState)
|
|
{
|
|
// Add the output to the job
|
|
for (int i = 0; i < results.Count; i++)
|
|
{
|
|
this.WriteObject(results[i]);
|
|
}
|
|
|
|
// And the errors
|
|
Error.Clear();
|
|
int currentErrorIndex = 0;
|
|
var errors = (ArrayList)actionState.PSVariable.Get("error").Value;
|
|
errors.Reverse();
|
|
|
|
for (int i = 0; i < errors.Count; i++)
|
|
{
|
|
var error = (ErrorRecord)errors[i];
|
|
if (currentErrorIndex == _highestErrorIndex)
|
|
{
|
|
this.WriteError(error);
|
|
_highestErrorIndex++;
|
|
}
|
|
|
|
currentErrorIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|