// // 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 results = new List(); // $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. CommandProcessorBase.CheckForSevereException(e); 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 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++; } } } }