Sign in to follow this  
Followers 0
wraithdu

SHChangeNotifyRegister()

13 posts in this topic

#1 ·  Posted (edited)

I've been messing with this all night, and finally got it working. The SHChangeNotifyRegister() function allows your GUI window to receive any of the notifications available from the SHChangeNotify() function - http://msdn.microsoft.com/en-us/library/bb762118(VS.85).aspx

In my example, I get notified of all file and folder deletes for the folder C:\test. The folder must exist before calling the function. Set the fRecursive parameter of the shnotifystruct to 1 to be notified of changes recursively from the root folder. Set the pidl parameter to 0 to receive notifications from the entire filesystem.

You must call CoTaskMemFree() for each PIDL created with ILCreateFromPath(), and SHChangeNotifyDeregister() for each path registered with SHChangeNotifyRegister(). You can register more than one path to monitor multiple folders.

In the notification, lParam holds the notifications message, and wParam holds a pointer to a SHNOTIFY struct which holds 2 dword values. dwItem1 and dwItem2 contain information according to the message received, based on the SHChangeNotify() function (see web page above for different values).

To see it work, run the script, then create a new file in C:\test and delete it.

#include <WinAPI.au3>

Opt("TrayAutoPause", 0)

Global Const $SHCNE_RENAMEITEM = 0x0001
Global Const $SHCNE_CREATE = 0x0002
Global Const $SHCNE_DELETE = 0x0004
Global Const $SHCNE_MKDIR = 0x0008
Global Const $SHCNE_RMDIR = 0x0010
Global Const $SHCNE_UPDATEDIR = 0x1000
Global Const $SHCNE_UPDATEITEM = 0x2000
Global Const $SHCNE_UPDATEIMAGE = 0x8000
Global Const $SHCNE_RENAMEFOLDER = 0x20000
Global Const $SHCNE_ALLEVENTS = 0x7FFFFFFF

OnAutoItExitRegister("OnAutoItExit")

$SHNOTIFY = _WinAPI_RegisterWindowMessage("shchangenotifymsg")

$gui = GUICreate("")
GUIRegisterMsg($SHNOTIFY, "MY_SHNOTIFY")

If Not FileExists("C:\test") Then DirCreate("C:\test")
$ppidl = DllCall("shell32.dll", "ptr", "ILCreateFromPathW", "wstr", "C:\test")
ConsoleWrite($ppidl[0] & @CRLF)
$shnotifystruct = DllStructCreate("ptr pidl; int fRecursive")
DllStructSetData($shnotifystruct, "pidl", $ppidl[0])
DllStructSetData($shnotifystruct, "fRecursive", 1)
$register = DllCall("shell32.dll", "int", "SHChangeNotifyRegister", "hwnd", $gui, _
        "int", 0x0003, _
        "long", BitOR($SHCNE_DELETE, $SHCNE_RMDIR), _
        "uint", $SHNOTIFY, _
        "int", 1, _
        "ptr", DllStructGetPtr($shnotifystruct))
DllCall("ole32.dll", "none", "CoTaskMemFree", "ptr", $ppidl[0])
ConsoleWrite("register:  " & $register[0] & @CRLF)

While 1
    Sleep(1000)
WEnd

Func OnAutoItExit()
    $ret = DllCall("shell32.dll", "int", "SHChangeNotifyDeregister", "ulong", $register[0])
    ConsoleWrite("deregister:  " & $ret[0] & @CRLF)
EndFunc

Func MY_SHNOTIFY($hWnd, $Msg, $wParam, $lParam)
    Local $path
    
    ConsoleWrite("notify:  " & $lParam & @CRLF)
    $path = DllStructCreate("dword dwItem1; dword dwItem2", $wParam)
    $ret = DllCall("shell32.dll", "int", "SHGetPathFromIDListW", "ptr", DllStructGetData($path, "dwItem1"), "wstr", "")
    ConsoleWrite($ret[2] & @CRLF)
EndFunc
Edited by wraithdu

Share this post


Link to post
Share on other sites



#2 ·  Posted (edited)

Interesting note - AutoIt's FileMove() func does not trigger the SHCNE_CREATE notification in the destination dir. DirMove(), on the other hand, does trigger the SHCNE_MKDIR notification in the destination dir. AutoIt bug maybe?

Edited by wraithdu

Share this post


Link to post
Share on other sites

#3 ·  Posted (edited)

Here's an example of registering more than one folder at a time. It was tricky to figure out how exactly the function wanted the array, but I finally got it.

#include <WinAPI.au3>

Opt("TrayAutoPause", 0)

Global Const $SHCNE_RENAMEITEM = 0x0001
Global Const $SHCNE_CREATE = 0x0002
Global Const $SHCNE_DELETE = 0x0004
Global Const $SHCNE_MKDIR = 0x0008
Global Const $SHCNE_RMDIR = 0x0010
Global Const $SHCNE_UPDATEDIR = 0x1000
Global Const $SHCNE_UPDATEITEM = 0x2000
Global Const $SHCNE_UPDATEIMAGE = 0x8000
Global Const $SHCNE_RENAMEFOLDER = 0x20000
Global Const $SHCNE_ALLEVENTS = 0x7FFFFFFF

OnAutoItExitRegister("OnAutoItExit")

$SHNOTIFY = _WinAPI_RegisterWindowMessage("shchangenotifymsg")

$gui = GUICreate("")
GUIRegisterMsg($SHNOTIFY, "MY_SHNOTIFY")

If Not FileExists("C:\test") Then DirCreate("C:\test")
If Not FileExists("C:\test2") Then DirCreate("C:\test2")
$ppidl = DllCall("shell32.dll", "ptr", "ILCreateFromPathW", "wstr", "C:\test")
ConsoleWrite($ppidl[0] & @CRLF)
$ppidl2 = DllCall("shell32.dll", "ptr", "ILCreateFromPathW", "wstr", "C:\test2")
ConsoleWrite($ppidl2[0] & @CRLF)
$shArray = DllStructCreate("double; double"); 16 bytes = two 8 byte shnotifystructs in an array
; == this could also be done as DllStructCreate("double[2]"), $pshArray = DllStructGetPtr($shArray)
; == then below would be
; == $shnotifystruct = DllStructCreate("ptr pidl; int fRecursive", $pshArray)
; == $shnotifystruct2 = DllStructCreate("ptr pidl; int fRecursive", $pshArray + 8) -> ptr to array plus 8 bytes
; == for many folders, either method could be achieved by use of loops
$shnotifystruct = DllStructCreate("ptr pidl; int fRecursive", DllStructGetPtr($shArray, 1)); first struct, first element
ConsoleWrite("error1:  " & @error & @CRLF)
DllStructSetData($shnotifystruct, "pidl", $ppidl[0])
DllStructSetData($shnotifystruct, "fRecursive", 0)
$shnotifystruct2 = DllStructCreate("ptr pidl; int fRecursive", DllStructGetPtr($shArray, 2)); second struct, second element
ConsoleWrite("error2:  " & @error & @CRLF)
DllStructSetData($shnotifystruct2, "pidl", $ppidl2[0])
DllStructSetData($shnotifystruct2, "fRecursive", 0)
$register = DllCall("shell32.dll", "int", "SHChangeNotifyRegister", "hwnd", $gui, _
        "int", 0x0003, _
        "long", BitOR($SHCNE_CREATE, $SHCNE_DELETE, $SHCNE_MKDIR, $SHCNE_RMDIR), _
        "uint", $SHNOTIFY, _
        "int", 2, _; number of shnotifystructs in the array
        "ptr", DllStructGetPtr($shArray)); ptr to the array
; free each created PIDL
DllCall("ole32.dll", "none", "CoTaskMemFree", "ptr", $ppidl[0])
DllCall("ole32.dll", "none", "CoTaskMemFree", "ptr", $ppidl2[0])
$shArray = 0
$shnotifystruct = 0
$shnotifystruct2 = 0
ConsoleWrite("register:  " & $register[0] & @CRLF)

While 1
    Sleep(1000)
WEnd

Func OnAutoItExit()
; deregister the window
    $ret = DllCall("shell32.dll", "int", "SHChangeNotifyDeregister", "ulong", $register[0])
    ConsoleWrite("deregister:  " & $ret[0] & @CRLF)
EndFunc

Func MY_SHNOTIFY($hWnd, $Msg, $wParam, $lParam)
    Local $path
    
    ConsoleWrite("notify:  " & $lParam & @CRLF)
    $path = DllStructCreate("dword dwItem1; dword dwItem2", $wParam)
    $ret = DllCall("shell32.dll", "int", "SHGetPathFromIDListW", "ptr", DllStructGetData($path, "dwItem1"), "wstr", "")
    ConsoleWrite($ret[2] & @CRLF)
EndFunc
Edited by wraithdu

Share this post


Link to post
Share on other sites

#4 ·  Posted (edited)

good job!

it's really cooooooooooool!!

Edited by cloim

Share this post


Link to post
Share on other sites

#5 ·  Posted (edited)

a perfect code so far, but i can't get it to register file changes even $SHCNE_ALLEVENTS does not the trick, did i miss something?

or isn't it possible to track file internal changes ( like a modified logfile) with it?

Edited by JRSmile

$a=StringSplit("547275737420796F757220546563686E6F6C75737421","")For $b=1 To UBound($a)+(-1*-1*-1)step(2^4/8);&$b+=1*2/40*µ&Asc(4)Assign("c",Eval("c")&Chr(Dec($a[$b]&$a[$b+1])))''Chr("a")&"HI"Next;time_U&r34d,ths,U-may=get$the&c.l.u.e;b3st-regards,JRSmile;MsgBox(0x000000,"",Eval("c"));PiEs:d0nt+*b3.s4d.4ft3r.1st-try:-)

Share this post


Link to post
Share on other sites

No, you didn't miss anything. I can't get it to notify either. This seems like a very unreliable function for file/folder level stuff. Shame...

Share this post


Link to post
Share on other sites

Edit... aha! Change the second param of the DllCall to '"int", 0x0003' and things start to make more sense. You'll have to work out how to interpret the different messages from the SHChangeNotify documentation, as my example doesn't handle all the possibilities properly.

Share this post


Link to post
Share on other sites

i can't see what 0x0003 would do, since:

SHCNRF_InterruptLevel

0x0001. Interrupt level notifications from the file system.

SHCNRF_ShellLevel

0x0002. Shell-level notifications from the shell.

Interrupts are file system and shell are the same we got already, are interrupts fired when modifying a file?i ask because i did not get any when testing :idea:

any idea?


$a=StringSplit("547275737420796F757220546563686E6F6C75737421","")For $b=1 To UBound($a)+(-1*-1*-1)step(2^4/8);&$b+=1*2/40*µ&Asc(4)Assign("c",Eval("c")&Chr(Dec($a[$b]&$a[$b+1])))''Chr("a")&"HI"Next;time_U&r34d,ths,U-may=get$the&c.l.u.e;b3st-regards,JRSmile;MsgBox(0x000000,"",Eval("c"));PiEs:d0nt+*b3.s4d.4ft3r.1st-try:-)

Share this post


Link to post
Share on other sites

I guess seangriffin gave the answer here...

Shell Notifications

Here's an excerpt:

"...The Origin of Events

So now, you know how to receive any of these shell notifications that are floating around, but who is actually generating them? According to the documentation, 'An application should use this function (SHChangeNotify) if it performs an action that may affect the shell'. But that seems to be a bit of wishful thinking. I can't imagine there are many applica tion developers that really give a damn whether the shell is kept informed of their actions...

...The result is that these notifications tend to be a little bit unreliable. The likelyhood of you getting an event for something, may depend on what explorer windows happen to be open at the time. The shell also only has a 10 item event buffer, and may replace some events with a generic SHCNE_UPDATEDIR in case of an overflow. In short: don't depend on these notifications for any mission-critical applications..."

Share this post


Link to post
Share on other sites

I guess seangriffin gave the answer here...

Thats soooo typical for a non perfect developer world ...

$a=StringSplit("547275737420796F757220546563686E6F6C75737421","")For $b=1 To UBound($a)+(-1*-1*-1)step(2^4/8);&$b+=1*2/40*µ&Asc(4)Assign("c",Eval("c")&Chr(Dec($a[$b]&$a[$b+1])))''Chr("a")&"HI"Next;time_U&r34d,ths,U-may=get$the&c.l.u.e;b3st-regards,JRSmile;MsgBox(0x000000,"",Eval("c"));PiEs:d0nt+*b3.s4d.4ft3r.1st-try:-)

Share this post


Link to post
Share on other sites

Once I changed that value from 0x0002 to 0x0003, I started getting the notifications I was missing while modifying a file (text file -> change -> save), and notifications for renames, etc. I tested with Explorer and Notepad++. I dont know what the InterruptLevel really means either, but it gave me my missing notifications.

Share this post


Link to post
Share on other sites

Ok i tested with the windows notepad... And there were no notifies ... So bad programs like micdosoft own tools do not notify :-) so i will have to test with the app i use in production :-) good news thank you very much.


$a=StringSplit("547275737420796F757220546563686E6F6C75737421","")For $b=1 To UBound($a)+(-1*-1*-1)step(2^4/8);&$b+=1*2/40*µ&Asc(4)Assign("c",Eval("c")&Chr(Dec($a[$b]&$a[$b+1])))''Chr("a")&"HI"Next;time_U&r34d,ths,U-may=get$the&c.l.u.e;b3st-regards,JRSmile;MsgBox(0x000000,"",Eval("c"));PiEs:d0nt+*b3.s4d.4ft3r.1st-try:-)

Share this post


Link to post
Share on other sites

When I switch to SHCNE_ALLEVENTS, I'm getting SHCNE_UPDATEITEM events for changing text file contents in Notepad. I'm on Win7 Ult 32-bit.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0