Some examples of handling [WMI] events through [TWAPI COM support]. See [TWAPI and WMI] for examples related to retrieving WMI data. [WMI] can be configured to monitor system events and send notifications to an event sink when the events occur. '''Note that asynchronous notifications in WMI have security issues depending on what you do with the data.''' Refer to the WMI documentation at [http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/calling_a_method.asp] for details and steps to secure the notifications. The examples below illustrate how to set up the event sink and associated handlers using [TWAPI COM support]. Remember to do a $wmi -destroy at the end when you no longer need WMI. Also, the Tcl event loop must be running, either through Tk or through a vwait command. '''[Maiki] - 2011-05-06 09:08:40''' '''Assumes twapi 3.0 - will need to be modified for earlier versions''' ====== proc start_process_tracker {} { # Get local WMI root provider set ::wmi [twapi::_wmi] # Create an WMI event sink set ::event_sink [twapi::comobj wbemscripting.swbemsink] # Attach our handler to it set ::event_sink_id [$::event_sink -bind process_start_handler] # Associate the sink with a query that polls every 1 sec for process # starts. $::wmi ExecNotificationQueryAsync [$::event_sink -interface] "select * from __InstanceCreationEvent within 1 where TargetInstance ISA 'Win32_Process'" } proc process_start_handler {wmi_event args} { if {$wmi_event eq "OnObjectReady"} { # First arg is a IDispatch interface of the event object # Create a TWAPI COM object out of it set ifc [lindex $args 0] IUnknown_AddRef $ifc; # Must hold ref before creating comobj set event_obj [twapi::comobj_idispatch $ifc] # Get and print the Name property puts "Process [$event_obj ProcessID] [$event_obj ProcessName] started at [clock format [large_system_time_to_secs [$event_obj TIME_CREATED]] -format {%x %X}]" # Get rid of the event object $event_obj -destroy } } ====== ---- '''Avoiding Hangs in Queries''' [neb] 2011-07-19 I've been using a set of wmi queries on a large number of machines. Occasionally, they will hang for no apparent reason. Everything works, connects, etc, but the query just never returns. This is an oft-asked-about issue, online; and Microsoft never saw fit to offer any way to cancel or timeout a query. You can, however, use an Asynchronous (as opposed to a 'Semisynchronous') query using close to the same process as above. The following code is a knock-off of this MSDN example: http://msdn.microsoft.com/en-us/library/aa392295%28v=vs.85%29.aspx ====== package require twapi set ::bdone 0 set count 0 proc change_handler {wmi_event args} { switch $wmi_event { OnObjectReady { # Create a TWAPI COM object out of the IDispatch interface (first arg) set event_obj [twapi::comobj_idispatch [lindex $args 0]] puts "Name: [$event_obj Name]" incr ::count } OnCompleted { set ::bdone 1 } } } set eventsink [twapi::comobj wbemscripting.swbemsink] set eventsink_id [$eventsink -bind change_handler] set wmi [twapi::_wmi] after 12 {if {!$::bdone} {set ::bdone 2}} set wql {SELECT Name FROM Win32_Process} $wmi ExecQueryAsync [$eventsink -interface] $wql vwait bdone $eventsink -unbind $eventsink_id $eventsink -destroy $wmi -destroy after 3000 puts "$count objects" if {$::bdone==2} {puts timedout} ====== 'after 12' corresponds to roughly how long it takes the machine I am on to return the first record. Adding one ms results in the rest of the list all burped out at once. A more annotated version follows. Note it also uses '$eventsink Cancel' for the timeout action, rather than relying on the variable. Canceling triggers the 'OnCompleted' event, with a result value of 0x80041032 (user cancel). It also seems to work faster: Cancel will interrupt the list in around 6ms on my machine, vs 12 for the version above. There should be a way to turn the objErrorObject parameter into a COM or SWbemLastError object (which would have a GetObjectText_ method), but I haven't worked it out. ====== package require twapi set ::bdone 0 set count 0 proc change_handler {wmi_event args} { switch $wmi_event { # http://msdn.microsoft.com/en-us/library/gg196623%28v=VS.85%29.aspx OnObjectReady { # objObject, objAsyncContext # Create a TWAPI COM object out of the IDispatch interface (first arg) set event_obj [twapi::comobj_idispatch [lindex $args 0]] puts "Name: [$event_obj Name]" incr ::count #$event_obj -destroy ;# destroying locks up the next creation. } OnCompleted { # iHResult, objErrorObject, objAsyncContext set ::bdone 1 if {[lindex $args 0]} { puts "Completed with error of 0x[format %08X [lindex $args 0]] (0x80041032 = user cancel)" } } OnProgress { # iUpperBound, iCurrent, strMessage, objWbemAsyncContext # Needs the '128' in the ExecQueryAsync call (4th param). Not all queries supply progress info. puts Progress } OnObjectPut { # objWbemObjectPath, objWbemAsyncContext puts Put } default { puts "Unhandled event: $wmi_event" } } } set eventsink [twapi::comobj wbemscripting.swbemsink] set eventsink_id [$eventsink -bind change_handler] set wmi [twapi::_wmi] after 6 {$eventsink Cancel; puts "query timed out."} set wql {SELECT Name FROM Win32_Process} # params: eventsink, query string, must be 'WQL', 128 (0x80) = wbemFlagSendStatus $wmi ExecQueryAsync [$eventsink -interface] $wql WQL 128 vwait bdone puts "unmaking objects" $eventsink -unbind $eventsink_id $eventsink -destroy $wmi -destroy puts "Pausing to make sure everything is canceled." after 3000 puts "$count objects" ====== One thing I am worried about with these is that we set event_obj on each record returned, but I can't destroy it. Uncommenting the destroy line stops the query results at that point. In VBS examples online, they don't worry about destroying the objects--but they also don't have to create them. I'm not certain if the result of comobj_idispatch gets re-used, and then dies when it goes out of scope, or if it's creating a new object at every iteration (and how to delete it), and therefore these scripts have a memory leak. ---- [Category Windows] | [Category System Administration] ----