Strange behavior with WMI SWbemSink

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
tester
Posts: 84
Joined: 10 Jun 2021, 23:03

Strange behavior with WMI SWbemSink

Post by tester » 24 Sep 2022, 04:57

Hello,

While I'm experimenting with WMI's ExecNotificationQueryAsync() COM method, I've encountered some strange behavior. Please run the code below. And create a test file in the A_ScriptDir\test directory and edit it to see if a tray notice appears. After that, reload the script several times. Then modify the file under A_ScriptDir\test again. If you can reproduce the problem, you'll see the Edited notice multiple times for one modification. Moreover, if you leave the script running for a while, the __InstanceModificationEvent event gets triggered again and again with some random intervals without any modifications.

Code: Select all

#Persistent
sPathDir1  := A_ScriptDir "\test"
FileCreateDir, % sPathDir1
; oFileMonitor := new FileMonitor.Common( "NotifyFileChange", sPathDir1 )
oFileMonitorEdit   := new FileMonitor.Edit( "NotifyFileChange", sPathDir1 )
oFileMonitorCreate := new FileMonitor.Create( "NotifyFileChange", sPathDir1 )
oFileMonitorDelete := new FileMonitor.Delete( "NotifyFileChange", sPathDir1 )

return

~Esc::ExitApp

NotifyFileChange( aParams* ) {
    _oEvent := aParams.1
    SplitPath, % aParams.1.TargetInstance.Name, sFileName
    if ( "__InstanceModificationEvent" == _oEvent.Path_.Class ) {
        TrayTip, Edited, % sFileName "`n" _oEvent.Path_.Class
        return
    }
    if ( "__InstanceCreationEvent" == _oEvent.Path_.Class ) {
        TrayTip, Created, % sFileName "`n" _oEvent.Path_.Class
        return
    }
    if ( "__InstanceDeletionEvent" == _oEvent.Path_.Class ) {
        TrayTip, Deleted, % sFileName "`n" _oEvent.Path_.Class
        return
    }
    TrayTip, Unknown Class, % sFileName "`n" . _oEvent.Path_.Class
}

class FileMonitor {

    class Delete extends FileMonitor.Common {
        sQueryFROM := "__InstanceDeletionEvent"
    }
    class Create extends FileMonitor.Common {
        sQueryFROM := "__InstanceCreationEvent"
    }
    class Edit extends FileMonitor.Common {
        sQueryFROM := "__InstanceModificationEvent"
    }

    class Common {

        sQueryFROM   := "__InstanceOperationEvent"
        oCOMWMI      := {}
        oCOMSWemSink := {}

        __New( cb, sPathDir, nInterval:=1 ) {
            this.oCOMWMI      := ComObjGet( "winmgmts:" )
            this.oCOMSWemSink := ComObjCreate( "WbemScripting.SWbemSink" )
            ComObjConnect( this.oCOMSWemSink, new this.Connect( cb ) )
            this.oCOMWMI.ExecNotificationQueryAsync( this.oCOMSWemSink, this.getWMIQuery( sPathDir, nInterval ) )
            OnExit( ObjBindMethod( this, "stop" ) )
        }

        getWMIQuery( sPathDir, nInterval ) {
            SplitPath, sPathDir,,,,, _sDriveLetter
            return "SELECT * From " this.sQueryFROM
                . " WITHIN " nInterval
                . " WHERE Targetinstance ISA 'CIM_DataFile'"
                . " AND TargetInstance.Drive='" _sDriveLetter "'"
                . " AND TargetInstance.Path='" RegExReplace( sPathDir, "[A-Z]:\\|((?<!\\)\\(?!\\)|(?<!\\)$)", "\\" ) "'"
        }

        stop() {
            this.oCOMSWemSink.Cancel()
            ComObjConnect( this.oCOMSWemSink )
        }

        class Connect {
            fnCallback := {}
            __New( fnCallback ) {
                this.fnCallback := fnCallback
            }
            OnObjectReady( aParams* ) {
                if StrLen( this.fnCallback ) {
                    Func( this.fnCallback ).Call( aParams* )
                    return
                }
                this.fnCallback.Call( aParams* )
            }
        }
    }
}
If I uncomment oFileMonitor := new FileMonitor.Common( "NotifyFileChange", sPathDir1 ) and comment out

Code: Select all

oFileMonitorEdit   := new FileMonitor.Edit( "NotifyFileChange", sPathDir1 )
oFileMonitorCreate := new FileMonitor.Create( "NotifyFileChange", sPathDir1 )
oFileMonitorDelete := new FileMonitor.Delete( "NotifyFileChange", sPathDir1 )
the notification becomes normal.

I've searched the web and came across this topic. It says the WMI query is still running in the background after the script exits so you need to use the Cancel() method. I'm not sure if it is the case here. I used Cancel() in the above example but the strange behavior still persists.

tester
Posts: 84
Joined: 10 Jun 2021, 23:03

Re: Strange behavior with WMI SWbemSink

Post by tester » 25 Sep 2022, 01:54

I had to inspect what was passed and it seems __InstanceModificationEvent is triggered even when a file is accessed. I could make the Edited notifications became quiet this way.

Code: Select all

    if ( "__InstanceModificationEvent" == _oEvent.Path_.Class ) {
        _oThis     := _oEvent.TargetInstance
		_oPrevious := _oEvent.PreviousInstance
        _oChanged  := {}
        For _oPropertyPrevious in _oPrevious.Properties_ {
            if ( _oThis[ _oPropertyPrevious.Name ] == _oPropertyPrevious.Value ) {
                continue
            }
            _oChanged[ _oPropertyPrevious.Name ] := _oThis[ _oPropertyPrevious.Name ]
        }
        if ( ! _oChanged[ "LastModified" ] ) {
            return
        }
        TrayTip, Edited, % sFileName
        return
    }
But the question still remains. Is it safe to exit the script without calling Cancel()? It seems okay as I observe CPU usage of WinPrvSE.exe.

Post Reply

Return to “Ask for Help (v1)”