MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4#pragma warning disable 1634, 16915 6using System.Collections;7using System.Collections.Generic;8using System.Diagnostics.CodeAnalysis;9using System.Linq;10using System.Management.Automation.Internal;11using System.Management.Automation.Runspaces;12using System.Reflection;13using System.Reflection.Emit;14using System.Threading;15 16namespace System.Management.Automation17{18 /// <summary>19 /// Represents the interface to the PowerShell eventing functionality.20 /// This class allows you to subscribe to, and receive events.21 /// </summary>22 public abstract class PSEventManager23 {24 /// <summary>Sequential event ID</summary>25 private int _nextEventId = 1;26 27 /// <summary>28 /// Returns a sequential event ID.29 /// </summary>30 protected int GetNextEventId()31 {32 return _nextEventId++;33 }34 35 /// <summary>36 /// Represents the interface to the PowerShell event queue.37 /// </summary>38 public PSEventArgsCollection ReceivedEvents { get; } = new PSEventArgsCollection();39 40 /// <summary>41 /// Gets the list of event subscribers.42 /// </summary>43 public abstract List<PSEventSubscriber> Subscribers { get; }44 45 /// <summary>46 /// Creates a PowerShell event.47 /// </summary>48 /// <param name="sourceIdentifier">49 /// An optional identifier that identifies the source event50 /// </param>51 /// <param name="sender">52 /// The object that generated this event53 /// </param>54 /// <param name="args">55 /// Any event-specific data associated with the event.56 /// </param>57 /// <param name="extraData">58 /// Any additional data you wish to attach to the event59 /// </param>60 protected abstract PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData);61 62 /// <summary>63 /// Generate a PowerShell event.64 /// </summary>65 /// <param name="sourceIdentifier">66 /// An optional identifier that identifies the source event67 /// </param>68 /// <param name="sender">69 /// The object that generated this event70 /// </param>71 /// <param name="args">72 /// Any event-specific data associated with the event.73 /// </param>74 /// <param name="extraData">75 /// Any additional data you wish to attach to the event76 /// </param>77 public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData)78 {79 return this.GenerateEvent(sourceIdentifier, sender, args, extraData, false, false);80 }81 82 /// <summary>83 /// Generate a PowerShell event.84 /// </summary>85 /// <param name="sourceIdentifier">86 /// An optional identifier that identifies the source event87 /// </param>88 /// <param name="sender">89 /// The object that generated this event90 /// </param>91 /// <param name="args">92 /// Any event-specific data associated with the event.93 /// </param>94 /// <param name="extraData">95 /// Any additional data you wish to attach to the event96 /// </param>97 /// <param name="processInCurrentThread">98 /// True if the event should be triggered in current thread. False for the event99 /// to be triggered in a separate thread.100 /// </param>101 /// <param name="waitForCompletionInCurrentThread">102 /// Wait for the event and associated action to be processed and completed.103 /// </param>104 public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData,105 bool processInCurrentThread, bool waitForCompletionInCurrentThread)106 {107 PSEventArgs newEvent = CreateEvent(sourceIdentifier, sender, args, extraData);108 ProcessNewEvent(newEvent, processInCurrentThread, waitForCompletionInCurrentThread);109 110 return newEvent;111 }112 113 /// <summary>114 /// Adds a forwarded event to the current event manager.115 /// </summary>116 internal abstract void AddForwardedEvent(PSEventArgs forwardedEvent);117 118 /// <summary>119 /// Processes new events (which have either been generated by this instance or forwarded to it)120 /// </summary>121 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "InCurrent")]122 protected abstract void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread);123 124 /// <summary>125 /// Processes new events (which have either been generated by this instance or forwarded to it)126 /// </summary>127 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "InCurrent")]128 protected internal virtual void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread,129 bool waitForCompletionWhenInCurrentThread)130 {131 throw new NotImplementedException();132 }133 134 /// <summary>135 /// Get the event subscription that corresponds to an identifier136 /// </summary>137 /// <param name="sourceIdentifier">138 /// The identifier that identifies the source of the events139 /// </param>140 public abstract IEnumerable<PSEventSubscriber> GetEventSubscribers(string sourceIdentifier);141 142 /// <summary>143 /// Subscribes to an event on an object.144 /// </summary>145 /// <param name="source">146 /// The source object that defines the event147 /// </param>148 /// <param name="eventName">149 /// The event to subscribe150 /// </param>151 /// <param name="sourceIdentifier">152 /// An optional subscription identifier to help identify this event subscription153 /// </param>154 /// <param name="data">155 /// Any additional data you wish to attach to the event156 /// </param>157 /// <param name="action">158 /// Any action to be invoked when the event arrives159 /// </param>160 /// <param name="supportEvent">161 /// Any action to be invoked when the event arrives162 /// </param>163 /// <param name="forwardEvent">164 /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions165 /// </param>166 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]167 public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent);168 169 /// <summary>170 /// Subscribes to an event on an object.171 /// </summary>172 /// <param name="source">173 /// The source object that defines the event174 /// </param>175 /// <param name="eventName">176 /// The event to subscribe177 /// </param>178 /// <param name="sourceIdentifier">179 /// An optional subscription identifier to help identify this event subscription180 /// </param>181 /// <param name="data">182 /// Any additional data you wish to attach to the event183 /// </param>184 /// <param name="action">185 /// Any action to be invoked when the event arrives186 /// </param>187 /// <param name="supportEvent">188 /// Any action to be invoked when the event arrives189 /// </param>190 /// <param name="forwardEvent">191 /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions192 /// </param>193 /// <param name="maxTriggerCount">194 /// Indicate how many times the subscriber should be triggered before auto-unregister it195 /// 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 unregistered196 /// </param>197 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]198 public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount);199 200 /// <summary>201 /// Subscribes to an event on an object.202 /// </summary>203 /// <param name="source">204 /// The source object that defines the event205 /// </param>206 /// <param name="eventName">207 /// The event to subscribe208 /// </param>209 /// <param name="sourceIdentifier">210 /// An optional subscription identifier to help identify this event subscription211 /// </param>212 /// <param name="data">213 /// Any additional data you wish to attach to the event214 /// </param>215 /// <param name="handlerDelegate">216 /// Any action to be invoked when the event arrives217 /// </param>218 /// <param name="supportEvent">219 /// Any action to be invoked when the event arrives220 /// </param>221 /// <param name="forwardEvent">222 /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions223 /// </param>224 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]225 public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent);226 227 /// <summary>228 /// Subscribes to an event on an object.229 /// </summary>230 /// <param name="source">231 /// The source object that defines the event232 /// </param>233 /// <param name="eventName">234 /// The event to subscribe235 /// </param>236 /// <param name="sourceIdentifier">237 /// An optional subscription identifier to help identify this event subscription238 /// </param>239 /// <param name="data">240 /// Any additional data you wish to attach to the event241 /// </param>242 /// <param name="handlerDelegate">243 /// Any action to be invoked when the event arrives244 /// </param>245 /// <param name="supportEvent">246 /// Any action to be invoked when the event arrives247 /// </param>248 /// <param name="forwardEvent">249 /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions250 /// </param>251 /// <param name="maxTriggerCount">252 /// Indicate how many times the subscriber should be triggered before auto-unregister it253 /// 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 unregistered254 /// </param>255 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]256 public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount);257 258 /// <summary>259 /// Subscribes to an event on an object.260 /// </summary>261 /// <param name="source">262 /// The source object that defines the event263 /// </param>264 /// <param name="eventName">265 /// The event to subscribe266 /// </param>267 /// <param name="sourceIdentifier">268 /// An optional subscription identifier to help identify this event subscription269 /// </param>270 /// <param name="data">271 /// Any additional data you wish to attach to the event272 /// </param>273 /// <param name="handlerDelegate">274 /// Any action to be invoked when the event arrives275 /// </param>276 /// <param name="supportEvent">277 /// Any action to be invoked when the event arrives278 /// </param>279 /// <param name="forwardEvent">280 /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions281 /// </param>282 /// <param name="shouldQueueAndProcessInExecutionThread">283 /// True, if the handlerDelegate should be processed in the pipeline execution thread (if possible).284 /// </param>285 /// <param name="maxTriggerCount">286 /// Indicate how many times the subscriber should be triggered before auto-unregister it287 /// 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 unregistered288 /// The default value is zero289 /// </param>290 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]291 internal virtual PSEventSubscriber SubscribeEvent(object source,292 string eventName,293 string sourceIdentifier,294 PSObject data,295 PSEventReceivedEventHandler handlerDelegate,296 bool supportEvent,297 bool forwardEvent,298 bool shouldQueueAndProcessInExecutionThread,299 int maxTriggerCount = 0)300 {301 return SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount);302 }303 304 /// <summary>305 /// Unsubscribes from an event on an object.306 /// </summary>307 /// <param name="subscriber">308 /// The subscriber associated with the event subscription309 /// </param>310 public abstract void UnsubscribeEvent(PSEventSubscriber subscriber);311 312 /// <summary>313 /// This event is raised by the event manager to forward events.314 /// </summary>315 internal abstract event EventHandler<PSEventArgs> ForwardEvent;316 }317 318 /// <summary>319 /// Implementation of the PSEventManager for local runspaces.320 /// </summary>321 internal class PSLocalEventManager : PSEventManager, IDisposable322 {323 /// <summary>324 /// Creates a new instance of the PSEventManager class.325 /// </summary>326 internal PSLocalEventManager(ExecutionContext context)327 {328 _eventSubscribers = new Dictionary<PSEventSubscriber, Delegate>();329 _engineEventSubscribers = new Dictionary<string, List<PSEventSubscriber>>(StringComparer.OrdinalIgnoreCase);330 _actionQueue = new Queue<EventAction>();331 _context = context;332 }333 334 private readonly Dictionary<PSEventSubscriber, Delegate> _eventSubscribers;335 private readonly Dictionary<string, List<PSEventSubscriber>> _engineEventSubscribers;336 private readonly Queue<EventAction> _actionQueue;337 private readonly ExecutionContext _context;338 private int _nextSubscriptionId = 1;339 private readonly double _throttleLimit = 1;340 private int _throttleChecks = 0;341 342 // The assembly and module to hold our event registrations343 private AssemblyBuilder _eventAssembly = null;344 private ModuleBuilder _eventModule = null;345 private int _typeId = 0;346 347 /// <summary>348 /// Gets the list of event subscribers.349 /// </summary>350 public override List<PSEventSubscriber> Subscribers351 {352 get353 {354 List<PSEventSubscriber> subscribers = new List<PSEventSubscriber>();355 356 lock (_eventSubscribers)357 {358 foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys)359 {360 subscribers.Add(currentSubscriber);361 }362 }363 364 return subscribers;365 }366 }367 368 /// <summary>369 /// Subscribes to an event on an object.370 /// </summary>371 /// <param name="source">372 /// The source object that defines the event373 /// </param>374 /// <param name="eventName">375 /// The event to subscribe376 /// </param>377 /// <param name="sourceIdentifier">378 /// An optional subscription identifier to help identify this event subscription379 /// </param>380 /// <param name="data">381 /// Any additional data you wish to attach to the event382 /// </param>383 /// <param name="action">384 /// Any action to be invoked when the event arrives385 /// </param>386 /// <param name="supportEvent">387 /// Any action to be invoked when the event arrives388 /// </param>389 /// <param name="forwardEvent">390 /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions391 /// </param>392 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]393 public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent)394 {395 return SubscribeEvent(source, eventName, sourceIdentifier, data, action, supportEvent, forwardEvent, 0);396 }397 398 /// <summary>399 /// Subscribes to an event on an object.400 /// </summary>401 /// <param name="source">402 /// The source object that defines the event403 /// </param>404 /// <param name="eventName">405 /// The event to subscribe406 /// </param>407 /// <param name="sourceIdentifier">408 /// An optional subscription identifier to help identify this event subscription409 /// </param>410 /// <param name="data">411 /// Any additional data you wish to attach to the event412 /// </param>413 /// <param name="action">414 /// Any action to be invoked when the event arrives415 /// </param>416 /// <param name="supportEvent">417 /// Any action to be invoked when the event arrives418 /// </param>419 /// <param name="forwardEvent">420 /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions421 /// </param>422 /// <param name="maxTriggerCount">423 /// Indicate how many times the subscriber should be triggered before auto-unregister it424 /// 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 unregistered425 /// </param>426 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]427 public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount)428 {429 // Record this subscriber. This may just be a registration for engine events.430 PSEventSubscriber subscriber = new PSEventSubscriber(_context, _nextSubscriptionId++, source, eventName, sourceIdentifier, action, supportEvent, forwardEvent, maxTriggerCount);431 ProcessNewSubscriber(subscriber, source, eventName, sourceIdentifier, data, supportEvent, forwardEvent);432 subscriber.RegisterJob();433 434 return subscriber;435 }436 437 /// <summary>438 /// Subscribes to an event on an object.439 /// </summary>440 /// <param name="source">441 /// The source object that defines the event442 /// </param>443 /// <param name="eventName">444 /// The event to subscribe445 /// </param>446 /// <param name="sourceIdentifier">447 /// An optional subscription identifier to help identify this event subscription448 /// </param>449 /// <param name="data">450 /// Any additional data you wish to attach to the event451 /// </param>452 /// <param name="handlerDelegate">453 /// Any action to be invoked when the event arrives454 /// </param>455 /// <param name="supportEvent">456 /// Any action to be invoked when the event arrives457 /// </param>458 /// <param name="forwardEvent">459 /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions460 /// </param>461 /// <param name="shouldQueueAndProcessInExecutionThread">462 /// True, if the handlerDelegate should be processed in the pipeline execution thread (if possible).463 /// </param>464 /// <param name="maxTriggerCount">465 /// Indicate how many times the subscriber should be triggered before auto-unregister it466 /// 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 unregistered467 /// The default value is zero468 /// </param>469 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]470 internal override PSEventSubscriber SubscribeEvent(object source,471 string eventName,472 string sourceIdentifier,473 PSObject data,474 PSEventReceivedEventHandler handlerDelegate,475 bool supportEvent,476 bool forwardEvent,477 bool shouldQueueAndProcessInExecutionThread,478 int maxTriggerCount = 0)479 {480 PSEventSubscriber newSubscriber = SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount);481 newSubscriber.ShouldProcessInExecutionThread = shouldQueueAndProcessInExecutionThread;482 return newSubscriber;483 }484 485 /// <summary>486 /// Subscribes to an event on an object.487 /// </summary>488 /// <param name="source">489 /// The source object that defines the event490 /// </param>491 /// <param name="eventName">492 /// The event to subscribe493 /// </param>494 /// <param name="sourceIdentifier">495 /// An optional subscription identifier to help identify this event subscription496 /// </param>497 /// <param name="data">498 /// Any additional data you wish to attach to the event499 /// </param>500 /// <param name="handlerDelegate">501 /// Any action to be invoked when the event arrives502 /// </param>503 /// <param name="supportEvent">504 /// Any action to be invoked when the event arrives505 /// </param>506 /// <param name="forwardEvent">507 /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions508 /// </param>509 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]510 public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent)511 {512 return SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, 0);513 }514 515 /// <summary>516 /// Subscribes to an event on an object.517 /// </summary>518 /// <param name="source">519 /// The source object that defines the event520 /// </param>521 /// <param name="eventName">522 /// The event to subscribe523 /// </param>524 /// <param name="sourceIdentifier">525 /// An optional subscription identifier to help identify this event subscription526 /// </param>527 /// <param name="data">528 /// Any additional data you wish to attach to the event529 /// </param>530 /// <param name="handlerDelegate">531 /// Any action to be invoked when the event arrives532 /// </param>533 /// <param name="supportEvent">534 /// Any action to be invoked when the event arrives535 /// </param>536 /// <param name="forwardEvent">537 /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions538 /// </param>539 /// <param name="maxTriggerCount">540 /// Indicate how many times the subscriber should be triggered before auto-unregister it541 /// 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 unregistered542 /// </param>543 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]544 public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount)545 {546 // Record this subscriber. This may just be a registration for engine events.547 PSEventSubscriber subscriber = new PSEventSubscriber(_context, _nextSubscriptionId++, source, eventName, sourceIdentifier, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount);548 ProcessNewSubscriber(subscriber, source, eventName, sourceIdentifier, data, supportEvent, forwardEvent);549 subscriber.RegisterJob();550 551 return subscriber;552 }553 554 #region OnIdleProcessing555 556 private Timer _timer = null;557 private bool _timerInitialized = false;558 private bool _isTimerActive = false;559 /// <summary>560 /// We sample every 100ms to check if the engine is idle (currentlyRunningPipeline == null). If it's "idle"561 /// in four consecutive samples, then we believe it's actually idle. In this way we can avoid capturing possible562 /// pipeline transitions.563 /// </summary>564 private int _consecutiveIdleSamples = 0;565 566 /// <summary>567 /// Send on-idle event if the engine is idle. The property "AutoReset" of the timer is always false,568 /// so only one handler will be running at anytime. The timer will be enabled again if we can meet569 /// the following two conditions.570 /// 1. No PowerShell.OnIdle event is sent out571 /// 2. A PowerShell.OnIdle event is sent out, and there are still subscribers to the on-idle event.572 /// </summary>573 private void OnElapsedEvent(object source)574 {575 var localRunspace = _context.CurrentRunspace as LocalRunspace;576 577 if (localRunspace == null)578 {579 // This should never happen, the context should always reference to the local runspace580 _consecutiveIdleSamples = 0;581 return;582 }583 584 if (localRunspace.GetCurrentlyRunningPipeline() == null)585 {586 _consecutiveIdleSamples++;587 }588 else589 {590 _consecutiveIdleSamples = 0;591 }592 593 if (_consecutiveIdleSamples == 4)594 {595 _consecutiveIdleSamples = 0;596 lock (_engineEventSubscribers)597 {598 List<PSEventSubscriber> subscribers = null;599 if (_engineEventSubscribers.TryGetValue(PSEngineEvent.OnIdle, out subscribers) && subscribers.Count > 0)600 {601 // We send out on-idle event and keep enabling the timer only if there still are subscribers to the on-idle event602 GenerateEvent(PSEngineEvent.OnIdle, null, Array.Empty<object>(), null, false, false);603 EnableTimer();604 }605 else606 {607 _isTimerActive = false;608 }609 }610 }611 else612 {613 EnableTimer();614 }615 }616 617 private void InitializeTimer()618 {619 try620 {621 _timer = new Timer(OnElapsedEvent, null, Timeout.Infinite, Timeout.Infinite);622 }623 catch (ObjectDisposedException)624 {625 // The PSLocalEventManager is disposed, do nothing626 }627 }628 629 private void EnableTimer()630 {631 try632 {633 _timer.Change(100, Timeout.Infinite);634 }635 catch (ObjectDisposedException)636 {637 // The PSLocalEventManager is disposed, do nothing638 }639 }640 641 #endregion OnIdleProcessing642 643 private static readonly Dictionary<string, Type> s_generatedEventHandlers = new Dictionary<string, Type>();644 645 private void ProcessNewSubscriber(PSEventSubscriber subscriber, object source, string eventName, string sourceIdentifier, PSObject data, bool supportEvent, bool forwardEvent)646 {647 Delegate handlerDelegate = null;648 649 if (_eventAssembly == null)650 {651 _eventAssembly = AssemblyBuilder.DefineDynamicAssembly(652 new AssemblyName("PSEventHandler"),653 AssemblyBuilderAccess.Run);654 _eventModule = _eventAssembly.DefineDynamicModule("PSGenericEventModule");655 }656 657 string engineEventSourceIdentifier = null;658 bool isOnIdleEvent = false;659 // If we are subscribing to an event on an object, generate the supporting delegate660 // for that object.661 if (source != null)662 {663 // If the identifier starts with "PowerShell.", then it will collide with engine664 // events665 if ((sourceIdentifier != null) &&666 (sourceIdentifier.StartsWith("PowerShell.", StringComparison.OrdinalIgnoreCase)))667 {668 string errorMessage = StringUtil.Format(EventingResources.ReservedIdentifier, sourceIdentifier);669 670 throw new ArgumentException(errorMessage, nameof(sourceIdentifier));671 }672 673 EventInfo eventInfo = null;674 Type sourceType = source as Type ?? source.GetType();675 676 // PowerShell does not support WinRT events.677 if (WinRTHelper.IsWinRTType(sourceType))678 {679 throw new InvalidOperationException(EventingResources.WinRTEventsNotSupported);680 }681 682 // Retrieve the event from the object683 const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase;684 eventInfo = sourceType.GetEvent(eventName, bindingFlags);685 686 // If we can't find the event, throw an exception687 if (eventInfo == null)688 {689 string errorMessage = StringUtil.Format(EventingResources.CouldNotFindEvent, eventName);690 throw new ArgumentException(errorMessage, nameof(eventName));691 }692 693 // Try to set the EnableRaisingEvents property if it defines one694 PropertyInfo eventProperty = sourceType.GetProperty("EnableRaisingEvents");695 if (eventProperty != null && eventProperty.CanWrite)696 {697 try698 {699 object targetObject = eventProperty.SetMethod.IsStatic ? null : source;700 eventProperty.SetValue(targetObject, true);701 }702 catch (TargetInvocationException e)703 {704 if (e.InnerException != null)705 {706 throw e.InnerException;707 }708 else709 {710 throw;711 }712 }713 }714 715 // Get its invoke method, and register ourselves as a handler716 MethodInfo invokeMethod = eventInfo.EventHandlerType.GetMethod("Invoke");717 718 // We don't support non-void delegates, as the user has no way719 // to influence the result. In the .NET Framework, the only720 // events that return values are for extremely advanced scenarios:721 // System.ResolveEventHandler, and System.Reflection.ModuleResolveEventHandler.722 // For these, the Add-Type cmdlet will suffice.723 if (invokeMethod.ReturnType != typeof(void))724 {725 string errorMessage = EventingResources.NonVoidDelegateNotSupported;726 throw new ArgumentException(errorMessage, nameof(eventName));727 }728 729 // Cache generated event handlers (by type and event name) so that they don't bloat our730 // working set by recompiling event handlers for the same event.731 732 string eventHandlerKey = source.GetType().FullName + "|" + eventName;733 Type handlerType;734 735 lock (s_generatedEventHandlers)736 {737 if (!s_generatedEventHandlers.TryGetValue(eventHandlerKey, out handlerType))738 {739 handlerType = GenerateEventHandler(invokeMethod);740 s_generatedEventHandlers[eventHandlerKey] = handlerType;741 }742 }743 744 // And create an instance of the type745 ConstructorInfo constructor =746 handlerType.GetConstructor(new Type[] { typeof(PSEventManager), typeof(object), typeof(string), typeof(PSObject) });747 object handler = constructor.Invoke(new object[] { this, source, sourceIdentifier, data });748 MethodInfo eventDelegate = handlerType.GetMethod("EventDelegate", BindingFlags.Public | BindingFlags.Instance);749 handlerDelegate = eventDelegate.CreateDelegate(eventInfo.EventHandlerType, handler);750 751 eventInfo.AddEventHandler(source, handlerDelegate);752 }753 else754 {755 if (PSEngineEvent.EngineEvents.Contains(sourceIdentifier))756 {757 engineEventSourceIdentifier = sourceIdentifier;758 isOnIdleEvent = string.Equals(engineEventSourceIdentifier, PSEngineEvent.OnIdle, StringComparison.OrdinalIgnoreCase);759 }760 }761 762 lock (_eventSubscribers)763 {764 _eventSubscribers[subscriber] = handlerDelegate;765 if (engineEventSourceIdentifier == null)766 {767 return;768 }769 770 lock (_engineEventSubscribers)771 {772 if (isOnIdleEvent && !_timerInitialized)773 {774 InitializeTimer();775 _timerInitialized = true;776 }777 778 List<PSEventSubscriber> subscribers = null;779 if (!_engineEventSubscribers.TryGetValue(engineEventSourceIdentifier, out subscribers))780 {781 subscribers = new List<PSEventSubscriber>();782 _engineEventSubscribers.Add(engineEventSourceIdentifier, subscribers);783 }784 785 subscribers.Add(subscriber);786 787 // This subscriber is the only one in the idle event list, we enable the timer788 // since the subscriber could be the first one.789 if (isOnIdleEvent && !_isTimerActive)790 {791 EnableTimer();792 _isTimerActive = true;793 }794 }795 }796 }797 798 /// <summary>799 /// Unsubscribes from an event on an object.800 /// </summary>801 /// <param name="subscriber">802 /// The subscriber associated with the event subscription803 /// </param>804 public override void UnsubscribeEvent(PSEventSubscriber subscriber)805 {806 UnsubscribeEvent(subscriber, false);807 }808 809 /// <summary>810 /// Unsubscribes from an event on an object.811 /// </summary>812 /// <param name="subscriber">813 /// The subscriber associated with the event subscription814 /// </param>815 /// <param name="skipDraining">816 /// Indicate if we should skip draining817 /// </param>818 private void UnsubscribeEvent(PSEventSubscriber subscriber, bool skipDraining)819 {820 ArgumentNullException.ThrowIfNull(subscriber);821 822 Delegate existingSubscriber = null;823 lock (_eventSubscribers)824 {825 if (subscriber.IsBeingUnsubscribed || !_eventSubscribers.TryGetValue(subscriber, out existingSubscriber))826 {827 // Already unsubscribed by another thread or the subscriber doesn't exist828 return;829 }830 831 subscriber.IsBeingUnsubscribed = true;832 }833 834 if ((existingSubscriber != null) && (subscriber.SourceObject != null))835 {836 // Fire the unregistration handler837 subscriber.OnPSEventUnsubscribed(subscriber.SourceObject,838 new PSEventUnsubscribedEventArgs(subscriber));839 840 EventInfo eventInfo = null;841 842 Type sourceType = subscriber.SourceObject as Type ?? subscriber.SourceObject.GetType();843 844 const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase;845 eventInfo = sourceType.GetEvent(subscriber.EventName, bindingFlags);846 847 if ((eventInfo != null) && (existingSubscriber != null))848 {849 eventInfo.RemoveEventHandler(subscriber.SourceObject, existingSubscriber);850 }851 }852 853 // We don't need to drain pending actions when remove an auto-unregister subscriber854 // from ProcessPendingAction method855 if (!skipDraining)856 {857 // Drain any actions pending for this subscriber858 DrainPendingActions(subscriber);859 }860 861 // Stop the job862 subscriber.Action?.NotifyJobStopped();863 864 lock (_eventSubscribers)865 {866 _eventSubscribers.Remove(subscriber);867 if (PSEngineEvent.EngineEvents.Contains(subscriber.SourceIdentifier))868 {869 lock (_engineEventSubscribers)870 {871 _engineEventSubscribers[subscriber.SourceIdentifier].Remove(subscriber);872 }873 }874 }875 }876 877 /// <summary>878 /// Creates a PowerShell event.879 /// </summary>880 /// <param name="sourceIdentifier">881 /// An optional identifier that identifies the source event882 /// </param>883 /// <param name="sender">884 /// The object that generated this event885 /// </param>886 /// <param name="args">887 /// Any event-specific data associated with the event.888 /// </param>889 /// <param name="extraData">890 /// Any additional data you wish to attach to the event891 /// </param>892 protected override PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData)893 {894 return new PSEventArgs(null, _context.CurrentRunspace.InstanceId, GetNextEventId(), sourceIdentifier, sender, args, extraData);895 }896 897 /// <summary>898 /// Adds a forwarded event to the current event manager.899 /// </summary>900 internal override void AddForwardedEvent(PSEventArgs forwardedEvent)901 {902 forwardedEvent.EventIdentifier = GetNextEventId();903 904 ProcessNewEvent(forwardedEvent, false);905 }906 907 /// <summary>908 /// Processes new events (which have either been generated by this instance or forwarded to it)909 /// </summary>910 protected override void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread)911 {912 ProcessNewEvent(newEvent, processInCurrentThread, false);913 }914 915 /// <summary>916 /// Processes new events (which have either been generated by this instance or forwarded to it)917 /// </summary>918 protected internal override void ProcessNewEvent(PSEventArgs newEvent,919 bool processInCurrentThread,920 bool waitForCompletionWhenInCurrentThread)921 {922 if (processInCurrentThread)923 {924 ProcessNewEventImplementation(newEvent, true);925 ManualResetEventSlim waitHandle = newEvent.EventProcessed;926 if (waitHandle != null)927 {928 // Win8: 738767 In Win7, the processInCurrentThread parameter was used to be929 // called processSynchronously. Even though the parameter was called "processSynchronously",930 // the event and associated action were not processed synchronously..the event manager931 // just added the associated action into event queue. The action can get executed at a later932 // time depending on the throttle checks etc. In Win8, to support routing of ScriptBlock933 // invocation to the current runspace, we took dependency on eventing infrastructure and934 // this required ensuring the event and associated action be processed in the current thread935 // synchronously. The below while loop was added for that (win8: 530495). However, fix for936 // 530495 resulted in not responding for icm | % { icm } case and dynamic event/subscriptions scenarios.937 // To overcome that, changed "processSynchronously" parameter to "processInCurrentThread" and added938 // a new parameter "waitForCompletionWhenInCurrentThread" to trigger blocking for ScriptBlock939 // case.940 while (waitForCompletionWhenInCurrentThread && !waitHandle.Wait(250))941 {942 this.ProcessPendingActions();943 }944 945 waitHandle.Dispose();946 }947 }948 else949 {950 ThreadPool.QueueUserWorkItem(new WaitCallback((_) => ProcessNewEventImplementation(newEvent, false)));951 }952 }953 954 /// <summary>955 /// Called from ProcessNewEvent to actually process the event.956 /// </summary>957 private void ProcessNewEventImplementation(PSEventArgs newEvent, bool processSynchronously)958 {959 // Get the subscriber(s) for this event960 bool capturedEvent = false;961 List<PSEventSubscriber> actionsHandledInCurrentThread = new List<PSEventSubscriber>();962 List<PSEventSubscriber> subscribersWithoutActionOrHandler = new List<PSEventSubscriber>();963 foreach (PSEventSubscriber subscriber in GetEventSubscribers(newEvent.SourceIdentifier, true))964 {965 newEvent.ForwardEvent = subscriber.ForwardEvent;966 967 // If we found a subscriber and it has an action, queue it up968 if (subscriber.Action != null)969 {970 AddAction(new EventAction(subscriber, newEvent), processSynchronously);971 capturedEvent = true;972 }973 else if (subscriber.HandlerDelegate != null)974 {975 if (subscriber.ShouldProcessInExecutionThread)976 {977 AddAction(new EventAction(subscriber, newEvent), processSynchronously);978 }979 else980 {981 actionsHandledInCurrentThread.Add(subscriber);982 }983 984 capturedEvent = true;985 }986 else987 {988 subscribersWithoutActionOrHandler.Add(subscriber);989 }990 }991 992 foreach (PSEventSubscriber subscriber in actionsHandledInCurrentThread)993 {994 subscriber.HandlerDelegate(newEvent.Sender, newEvent);995 AutoUnregisterEventIfNecessary(subscriber);996 }997 998 // Otherwise, add it to the queue of unprocessed items, unless we are forwarding the event999 if (!capturedEvent)1000 {1001 if (newEvent.ForwardEvent)1002 {1003 OnForwardEvent(newEvent);1004 }1005 else1006 {1007 lock (ReceivedEvents.SyncRoot)1008 {1009 ReceivedEvents.Add(newEvent);1010 }1011 }1012 1013 foreach (PSEventSubscriber subscriber in subscribersWithoutActionOrHandler)1014 {1015 AutoUnregisterEventIfNecessary(subscriber);1016 }1017 }1018 }1019 1020 // Add an action to the event queue1021 private void AddAction(EventAction action, bool processSynchronously)1022 {1023 if (processSynchronously)1024 {1025 // This mutex will get set after the event is processed.1026 action.Args.EventProcessed = new ManualResetEventSlim();1027 }1028 1029 lock (((System.Collections.ICollection)_actionQueue).SyncRoot)1030 {1031 // If the engine isn't active, pulse the pipeline.1032 // When the engine starts up, it will pick up the pending events1033 _actionQueue.Enqueue(action);1034 }1035 1036 PulseEngine();1037 }1038 1039 // PowerShell support for async notifications happen through the1040 // CheckForInterrupts() method on ParseTreeNode. These are only called when1041 // the engine is active (and processing,) so the Pulse() method1042 // executes the equivalent of a NOP so that async events1043 // can be processed when the engine is idle.1044 private void PulseEngine()1045 {1046 try1047 {1048 ((LocalRunspace)_context.CurrentRunspace).Pulse();1049 }1050 catch (ObjectDisposedException)1051 { }1052 }1053 1054 /// <summary>1055 /// Process any pending actions. The parser calls this method before it executes any1056 /// parse tree node. If we are processing pending actions, we must block the parser.1057 /// To prevent starvation of the foreground script, we throttle the number of events1058 /// that we process while the parser is waiting. If the parser is not waiting, we1059 /// do not throttle the event processing.1060 /// </summary>1061 internal void ProcessPendingActions()1062 {1063 // We do this check quickly outside of the lock so that the1064 // main-line scenario is as fast as possible. Also, do the real work1065 // in a different method so that this method could be inlined.1066 if (_actionQueue.Count == 0)1067 return;1068 1069 ProcessPendingActionsImpl();1070 }1071 1072 private void ProcessPendingActionsImpl()1073 {1074 // Now, process pending actions1075 if (IsExecutingEventAction)1076 return;1077 1078 try1079 {1080 lock (_actionProcessingLock)1081 {1082 if (IsExecutingEventAction)1083 return;1084 1085 int processed = 0;1086 _throttleChecks++;1087 EventAction nextAction;1088 1089 while ((_throttleLimit * _throttleChecks) >= processed)1090 {1091 // Now, check for (and process) pending actions.1092 // Lock the collection so that it doesn't change from1093 // beneath us.1094 lock (((System.Collections.ICollection)_actionQueue).SyncRoot)1095 {1096 int queueCount = _actionQueue.Count;1097 1098 // Exit if the actions have already been processed1099 if (queueCount == 0)1100 return;1101 1102 nextAction = _actionQueue.Dequeue();1103 }1104 1105 bool addActionBackToActionQueue = false;1106 InvokeAction(nextAction, out addActionBackToActionQueue);1107 processed++;1108 1109 if (!addActionBackToActionQueue)1110 {1111 AutoUnregisterEventIfNecessary(nextAction.Sender);1112 }1113 }1114 1115 if (processed > 0)1116 _throttleChecks = 0;1117 }1118 }1119 finally1120 {1121 if (_actionQueue.Count > 0)1122 {1123 // If we still have work remaining, sleep a bit (to give1124 // other pipelines a chance to interrupt,) and try again.1125 // This is done on another thread, since we own the runspace lock1126 // at this point if we're being called from the pipeline1127 // teardown event. That can result in starvation of1128 // foreground threads that also want to use the runspace.1129 ThreadPool.QueueUserWorkItem(new WaitCallback(1130 (_) =>1131 {1132 System.Threading.Thread.Sleep(100);1133 this.PulseEngine();1134 }));1135 }1136 }1137 }1138 1139 /// <summary>1140 /// Auto unregister the subscriber if both 'RemainingTriggerCount' and 'RemainingActionsToProcess' become zero.1141 /// </summary>1142 private void AutoUnregisterEventIfNecessary(PSEventSubscriber subscriber)1143 {1144 bool removeSubscriber = false;1145 if (subscriber.AutoUnregister)1146 {1147 lock (subscriber)1148 {1149 subscriber.RemainingActionsToProcess--;1150 removeSubscriber = subscriber.RemainingTriggerCount == 0 &&1151 subscriber.RemainingActionsToProcess == 0;1152 }1153 }1154 1155 if (removeSubscriber)1156 {1157 UnsubscribeEvent(subscriber, true);1158 }1159 }1160 1161 private readonly object _actionProcessingLock = new object();1162 private EventAction _processingAction = null;1163 1164 /// <summary>1165 /// Drain any pending actions for a given subscriber.1166 /// This is a synchronous (and expensive) operation, but is1167 /// required so that unregistering for an event / stopping the1168 /// event job truly discontinues those event arrivals.1169 /// </summary>1170 internal void DrainPendingActions(PSEventSubscriber subscriber)1171 {1172 // We do this check quickly outside of the lock so that the1173 // main-line scenario is as fast as possible.1174 if (_actionQueue.Count == 0)1175 return;1176 1177 // Now, process pending actions1178 lock (_actionProcessingLock)1179 {1180 lock (((System.Collections.ICollection)_actionQueue).SyncRoot)1181 {1182 int queueCount = _actionQueue.Count;1183 1184 // Exit if the actions have already been processed1185 if (queueCount == 0)1186 return;1187 1188 bool needToScanAgain = false;1189 1190 do1191 {1192 EventAction[] pendingActions = _actionQueue.ToArray();1193 _actionQueue.Clear();1194 1195 foreach (EventAction pendingAction in pendingActions)1196 {1197 // Make sure an event action can unregister itself.1198 if ((pendingAction.Sender == subscriber) &&1199 (pendingAction != _processingAction))1200 {