WM_DEVICECHANGE & SHCNE_DISKEVENTS

by Pierre Bellisle

#COMPILE EXE '#Win#
#DIM ALL
#INCLUDE "Win32api.inc"
'#RESOURCE ".pbr"

GLOBAL hDlg AS DWORD

%Edit = 101

%WM_DEVICECHANGE             = &H219&  'Notifies application of change to hardware configuration of device or computer.
%DBT_CONFIGCHANGECANCELED    = &H19&   'Request to change the current configuration (dock or undock) has been canceled.
%DBT_CONFIGCHANGED           = &H18&   'Current configuration changed, due to a dock or undock.
%DBT_CUSTOMEVENT             = &H8006& 'Custom event.
%DBT_DEVICEARRIVAL           = &H8000& 'Device or piece of media has been inserted and is now available.
%DBT_DEVICEQUERYREMOVE       = &H8001& 'Permission requested to remove Device or media. (Any application can deny this request and cancel the removal.)
%DBT_DEVICEQUERYREMOVEFAILED = &H8002& 'Request to remove device or media canceled.
%DBT_DEVICEREMOVECOMPLETE    = &H8004& 'Device or Media removed.
%DBT_DEVICEREMOVEPENDING     = &H8003& 'Device or Media about to be removed. Cannot be denied.
%DBT_DEVICETYPESPECIFIC      = &H8005& 'Device-specific event.
%DBT_DEVNODES_CHANGED        = &H7&    'Device added or removed from the system.
%DBT_QUERYCHANGECONFIG       = &H17&   'Permission requested to change current configuration, dock or undock.
%DBT_USERDEFINED             = &HFFFF& 'User-Defined.

%DBTF_MEDIA = 1 'Media comings and goings
%DBTF_NET   = 2 'Network volume

%DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = &H4& 'All Interfaces.
%DEVICE_NOTIFY_WINDOW_HANDLE         = 0    'Notifications sent using WM_POWERBROADCAST with wParam = PBT_POWERSETTINGCHANGE.
%DEVICE_NOTIFY_SERVICE_HANDLE        = 1    'Notifications sent to HandlerEx callback function with dwControl = SERVICE_CONTROL_POWEREVENT and dwEventType = PBT_POWERSETTINGCHANGE.
%GWL_WNDPROC                         = (-4) 'Sets new address for the window procedure. Windows NT/2000/XP: Can not change attribute if window does not belong to same process as alling thread.
%UNSAFE_REMOVE                       = &H1C&'Unsafe removal of device.

%SHCNRF_INTERRUPTLEVEL     = &H0001???
%SHCNRF_SHELLLEVEL         = &H0002???
%SHCNRF_RECURSIVEINTERRUPT = &H1000???
%SHCNRF_NEWDELIVERY        = &H8000???

%DBT_DEVTYP_OEM             = &H0& 'Original Equipment Manufacturer(OEM) or Independent Hardware Vendor(IHV) defined device type. It's a DEV_BROADCAST_OEM structure.
%DBT_DEVTYP_DEVNODE         = &H1& 'Devnode number, specific to Windows 95.
%DBT_DEVTYP_VOLUME          = &H2& 'Logical volume. This structure is a DEV_BROADCAST_VOLUME structure.
%DBT_DEVTYP_PORT            = &H3& 'Port device, serial or parallel. It's a DEV_BROADCAST_PORT structure.
%DBT_DEVTYP_NET             = &H4& 'Network Resource UNC.
%DBT_DEVTYP_DEVICEINTERFACE = &H5& 'Class of devices. It's a DEV_BROADCAST_DEVICEINTERFACE structure.
%DBT_DEVTYP_HANDLE          = &H6& 'File system handle. It's a DEV_BROADCAST_HANDLE structure.

TYPE DEV_BROADCAST_HDR DWORD
 DbhSize        AS DWORD 'Size of Structure. If User-Defined event, this must be size of header, plus size of variable-length data in a DEV_BROADCAST_USERDEFINED structure.
 DbhDeviceType  AS DWORD 'Device Type, could be interface, handle, OEM, port or Volume.
 DbhReserved    AS DWORD 'Reserved.
END TYPE

TYPE DEV_BROADCAST_DEVICEINTERFACE DWORD
 DbDiSize       AS DWORD      'Size of Structure.
 DbDiDeviceType AS DWORD      'Set to %DBT_DEVTYP_DEVICEINTERFACE to get Interface Type.
 DbDiReserved   AS DWORD      'Reserved.
 DbDiClassguid  AS GUID       'GUID for Interface Device Class.
 DbDiName       AS ASCIIZ * 1 'When returned through %WM_DEVICECHANGE message, converted to ANSI as appropriate.
END TYPE

TYPE DEV_BROADCAST_HANDLE DWORD
 DbhSize        AS DWORD 'Size of Structure.
 DbhDeviceType  AS DWORD 'Set to %DBT_DEVTYP_HANDLE.
 DbhReserved    AS DWORD 'Reserved.
 DbhHandle      AS DWORD 'Handle in RegisterDeviceNotification function.
 DbhHdevNotify  AS DWORD 'Handle to device notification, returned by the RegisterDeviceNotification.
 DbhEventGuid   AS GUID  'Custom event GUID           Valid only for DBT_CUSTOMEVENT.
 DbhNameOffset  AS LONG  'The offset to event name.   Valid only for DBT_CUSTOMEVENT.
 DbhData(0)     AS BYTE  'Optional binary data.       Valid only for DBT_CUSTOMEVENT.
END TYPE

TYPE DEV_BROADCAST_OEM DWORD
 DboSize        AS DWORD 'Size of Structure.
 DboDeviceType  AS DWORD 'Set to %DBT_DEVTYPE_OEM.
 DboReserved    AS DWORD 'Reserved.
 DboIdentifier  AS DWORD 'OEM-specific identifier for the device.
 DboSuppfunc    AS DWORD 'OEM-specific function value. Possible values depend on the device.
END TYPE

TYPE DEV_BROADCAST_VOLUME DWORD
 Dbvsize        AS DWORD 'Size of Structure.
 DbvDeviceType  AS DWORD 'Set to %DBT_DEVTYP_VOLUME.
 DbvReserved    AS DWORD 'Reserved.
 DbvUnitmask    AS DWORD 'Logical drive, bit 0 is A:, bit 1 is B:...
 DbvFlags       AS WORD  'If %DBTF_MEDIA, change affects media in drive else change affects physical device or drive. %DBTF_NET = network volume.
END TYPE

TYPE DEV_BROADCAST_PORT DWORD
 DbpSize        AS DWORD 'Size of Structure.
 DbpDeviceType  AS DWORD 'Set to %DBT_DEVTYP_PORT - Set to check if a port device, see DEV_BROADCAST_DEVICEINTERFACE to find interface.
 DbpReserved    AS DWORD 'Reserved.
 DbpName        AS ASCIIZ * %MAX_PATH 'Device friendly name such as "Com1" and "Standard 28800 bps Modem", etc.
END TYPE

TYPE SHChangeNotifyEntry BYTE
 pidl       AS ITEMIDLIST POINTER
 fRecursive AS LONG
END TYPE

#IF %PB_REVISION < &H1000
DECLARE FUNCTION SHChangeNotifyRegister LIB "SHELL32.DLL" ALIAS "SHChangeNotifyRegister" _
(BYVAL HWND AS DWORD, BYVAL fSources AS LONG, BYVAL fEvents AS LONG, BYVAL wMsg AS DWORD, _
 BYVAL cEntries AS LONG, BYREF ChangeNotifyEntry AS SHChangeNotifyEntry) AS DWORD

DECLARE FUNCTION SHChangeNotifyDeregister LIB "SHELL32.DLL" ALIAS "SHChangeNotifyDeregister"(BYVAL ulID AS DWORD) AS LONG
#ENDIF
'______________________________________________________________________________

SUB TextAdd(BYVAL sText AS STRING)

 'Move the caret to the end of text.
 SendDlgItemMessage(hDlg, %Edit, %EM_SETSEL, -2, -2)

 sText = sText & $CRLF 'Add a CRLF if needed

 'Insert the string at caret position.
 SendDlgItemMessage(hDlg, %Edit, %EM_REPLACESEL, %TRUE, BYVAL STRPTR(sText))

END SUB
'______________________________________________________________________________

FUNCTION DeviceEventGet(BYVAL wParam AS DWORD, BYVAL pDevBroadCastHeader AS DEV_BROADCAST_HDR POINTER)AS STRING
 LOCAL pDevBroadcastDeviceinterface AS DEV_BROADCAST_DEVICEINTERFACE POINTER
 LOCAL pDevBroadcastHandle          AS DEV_BROADCAST_HANDLE POINTER
 LOCAL pDevBroadcastOem             AS DEV_BROADCAST_OEM POINTER
 LOCAL pDevBroadcastPorts           AS DEV_BROADCAST_PORT POINTER
 LOCAL pDevBroadcastVolume          AS DEV_BROADCAST_VOLUME POINTER
 #IF %PB_REVISION < &H1000
 LOCAL pzDbDiName                   AS ASCIIZ POINTER
 #ELSE
 LOCAL pzDbDiName                   AS WSTRINGZ POINTER
 #ENDIF
 LOCAL sMessage                     AS STRING
 LOCAL sDriveName                   AS STRING
 LOCAL sDriveFlags                  AS STRING
 LOCAL sManufacturer                AS STRING
 LOCAL sProduct                     AS STRING
 LOCAL sRevision                    AS STRING
 LOCAL sDiskSerial                  AS STRING
 LOCAL sTry                         AS STRING
 LOCAL Looper                       AS LONG
 LOCAL CharPos1                     AS LONG
 LOCAL CharPos2                     AS LONG

 SELECT CASE @pDevBroadCastHeader.DbhDevicetype

   CASE %DBT_DEVTYP_DEVICEINTERFACE 'DEV_BROADCAST_DEVICEINTERFACE - Device's class
     pDevBroadcastDeviceinterface = pDevBroadCastHeader
     pzDbDiName = pDevBroadcastDeviceinterface + 28
     sMessage = "DBT_DEVTYP_DEVICEINTERFACE - GUID: " & _
                GUIDTXT$(@pDevBroadcastDeviceinterface.DbDiClassguid) & _
                " - Interface name: " & @pzDbDiName
     sManufacturer = "None"
     sProduct      = sManufacturer
     sRevision     = sManufacturer
     sDiskSerial   = sManufacturer
     sTry = LCASE$(@pzDbDiName)
     CharPos1 = INSTR(sTry, "&ven_" )
     IF CharPos1 THEN
       CharPos2 = INSTR(CharPos1 ,sTry, "&prod_")
       IF CharPos2 THEN
         CharPos1 = CharPos1 + 5 'LEN("&ven_")
         IF (CharPos2 - CharPos1) THEN sManufacturer = MID$(@pzDbDiName, CharPos1, CharPos2 - CharPos1)
         CharPos1 = INSTR(CharPos2, sTry, "&rev_")
         IF CharPos1 THEN
           CharPos2 = CharPos2 + 6 'LEN("&prod_")
           IF (CharPos1 - CharPos2) THEN sProduct = MID$(@pzDbDiName, CharPos2, CharPos1 - CharPos2)
           CharPos2 = INSTR(CharPos1, sTry, "#")
           IF CharPos2 THEN
             CharPos1 = CharPos1 + 5 'LEN("&rev_")
             IF (CharPos2 - CharPos1) THEN sRevision = MID$(@pzDbDiName, CharPos1, CharPos2 - CharPos1)
             CharPos1 = INSTR(CharPos2, sTry, "&0#")
             IF CharPos1 THEN
               sDiskSerial = MID$(@pzDbDiName, CharPos2 + 1, CharPos1 - CharPos2 - 1)
               sMessage = sMessage & $CRLF & $TAB & $TAB & $TAB & "Manufacturer: " & sManufacturer & _
                          ", product: " & sProduct & ", revision: " & sRevision & ", serial number: " & sDiskSerial
             END IF
           END IF
         END IF
       END IF
     END IF

   CASE %DBT_DEVTYP_HANDLE 'DEV_BROADCAST_HANDLE - File system handle.
     pDevBroadcastHandle = pDevBroadCastHeader
     sMessage = "DBT_DEVTYP_HANDLE - Handle: " & STR$(@pDevBroadcastHandle.DbhHandle) & _
                " - Notify: "      & STR$(@pDevBroadcastHandle.DbhHdevNotify) & _
                IIF$((wParam = %DBT_CUSTOMEVENT), _
                " - Guid: "        & GUIDTXT$(@pDevBroadcastHandle.DbhEventGuid) & _  'Valid only for DBT_CUSTOMEVENT
                " - Name offset: " & STR$(@pDevBroadcastHandle.DbhNameOffset)& _      'Valid only for DBT_CUSTOMEVENT
                " - Data: "        & STR$(@pDevBroadcastHandle.DbhData(0)),  "")      'Valid only for DBT_CUSTOMEVENT

   CASE %DBT_DEVTYP_OEM 'DEV_BROADCAST_OEM - OEM or IHV device.
     pDevBroadcastOem = pDevBroadCastHeader
     sMessage = "DBT_DEVTYP_OEM - Identifier: " & STR$(@pDevBroadcastOem.DboIdentifier) & _
                " - SuppFunc: " & STR$(@pDevBroadcastOem.DboSuppFunc)

   CASE %DBT_DEVTYP_PORT 'DEV_BROADCAST_PORT - Port device like serial or parallel.
     pDevBroadcastPorts = pDevBroadCastHeader
     sMessage = "DBT_DEVTYP_PORT - Port name: " & @pDevBroadcastPorts.DbpName

   CASE %DBT_DEVTYP_VOLUME 'DEV_BROADCAST_VOLUME - Logical volume.
     pDevBroadcastVolume = pDevBroadCastHeader
     FOR Looper = 0 TO 25
       IF BIT(@pDevBroadcastVolume.DbvUnitMask, Looper) THEN
         sDriveName = CHR$(65 + Looper) & ":" '65 = "A"
         EXIT FOR
       END IF
     NEXT
     SELECT CASE @pDevBroadcastVolume.dbvFlags
       CASE 0           : sDriveFlags = "Local Drive"   'Local Volume
       CASE %DBTF_MEDIA : sDriveFlags = "Media Drive"   'Media Volume
       CASE %DBTF_NET   : sDriveFlags = "Network Drive" 'Network volume
     END SELECT
     sMessage = "DBT_DEVTYP_VOLUME - Drive name: " & sDriveName & " - Flag: " & sDriveFlags
     sMessage = sMessage & $CRLF & $TAB & $TAB & $TAB & "Drive letter: " & sDriveName & $SPC & sDriveFlags

 END SELECT
 FUNCTION = sMessage

END FUNCTION
'______________________________________________________________________________

CALLBACK FUNCTION DlgProc()
 LOCAL sMessage  AS STRING
 LOCAL zPath1    AS ASCIIZ * %MAX_PATH
 LOCAL zPath2    AS ASCIIZ * %MAX_PATH
 LOCAL ppIdList  AS DWORD POINTER 'ITEMIDLIST POINTER ~POINTER
 LOCAL pIdList1  AS DWORD POINTER 'ITEMIDLIST POINTER
 LOCAL pIdList2  AS DWORD POINTER 'ITEMIDLIST POINTER
 STATIC SelStart AS LONG
 STATIC SelEnd   AS LONG

 SELECT CASE AS LONG CBMSG

   CASE %WM_INITDIALOG
      PostMessage(GetDlgItem(hDlg, %Edit), %EM_SETSEL, -2, -2)

   CASE %WM_COMMAND
     SELECT CASE LOWRD(CBWPARAM)
       CASE %Edit
         IF (CBCTLMSG = %EN_KILLFOCUS) THEN
           SendMessage(CBLPARAM, %EM_GETSEL, VARPTR(SelStart), VARPTR(SelEnd))
         END IF
         IF (CBCTLMSG = %EN_SETFOCUS) THEN
           SendMessage(CBLPARAM, %EM_SETSEL, SelStart, SelEnd)
         END IF
     END SELECT

   CASE %WM_DEVICECHANGE 'wParam = Event, lParam = Data

     SELECT CASE CBWPARAM 'Event

       CASE %DBT_CONFIGCHANGECANCELED
         sMessage = "DBT_CONFIGCHANGECANCELED"

       CASE %DBT_CONFIGCHANGED
         sMessage = "DBT_CONFIGCHANGED"

       CASE %DBT_CUSTOMEVENT
         sMessage = "DBT_CUSTOMEVENT - " & DeviceEventGet(CBWPARAM, CBLPARAM)

       CASE %DBT_DEVICEARRIVAL
         sMessage = "DBT_DEVICEARRIVAL - " & DeviceEventGet(CBWPARAM, CBLPARAM)

       CASE %DBT_DEVICEQUERYREMOVE
         sMessage = "DBT_DEVICEQUERYREMOVE - " & DeviceEventGet(CBWPARAM, CBLPARAM)

       CASE %DBT_DEVICEQUERYREMOVEFAILED
         sMessage = "DBT_DEVICEQUERYREMOVEFAILED - " &  DeviceEventGet(CBWPARAM, CBLPARAM)

       CASE %DBT_DEVICEREMOVECOMPLETE
         sMessage = "DBT_DEVICEREMOVECOMPLETE - " & DeviceEventGet(CBWPARAM, CBLPARAM)

       CASE %DBT_DEVICEREMOVEPENDING
         sMessage = "DBT_DEVICEREMOVEPENDING" & DeviceEventGet(CBWPARAM, CBLPARAM)

       CASE %DBT_DEVICETYPESPECIFIC
         sMessage = "DBT_DEVICETYPESPECIFIC - " & DeviceEventGet(CBWPARAM, CBLPARAM)

       CASE %DBT_DEVNODES_CHANGED
         sMessage = "DBT_DEVNODES_CHANGED"

       CASE %DBT_QUERYCHANGECONFIG
         sMessage = "DBT_QUERYCHANGECONFIG"

       CASE %DBT_USERDEFINED
         sMessage = "DBT_USERDEFINED - " & DeviceEventGet(CBWPARAM, CBLPARAM)

       CASE ELSE
         sMessage = "DBT_UNKOWN - " & DeviceEventGet(CBWPARAM, CBLPARAM)

     END SELECT
     TextAdd(sMessage)

   CASE %WM_APP 'For media like memory card in a card reader and also for drive like a USB stick - SHChangeNotifyRegister SHCNE_DISKEVENTS notification
     'wParam is a pointer to two PIDLIST_ABSOLUTE pointers that can be NULL, depending on the event
     'lParam is the event
     #IF %PB_REVISION < &H1000
       ppIdList = CBWPARAM
       pIdList1 = @ppIdList
       ppIdList = ppIdList + 4
       pIdList2 = @ppIdList
     #ELSE
       pIdList1 = CBWPARAM
       pIdList2 = pIdList1 + 4
     #ENDIF

     IF pIdList1 THEN
       SHGetPathFromIDList(@pIdList1, zPath1)
       IF pIdList2 THEN SHGetPathFromIDList(@pIdList2, zPath2) ELSE zPath2 = "Nil"
     ELSE
       zPath1 = "Nil"
       zPath2 = zPath1
     END IF

     SELECT CASE CBLPARAM
       CASE %SHCNE_MEDIAINSERTED : sMessage = "SHCNE_MEDIAINSERTED - Media inserted: Drive letter is " & zPath1
       CASE %SHCNE_MEDIAREMOVED  : sMessage = "SHCNE_MEDIAREMOVED - Media removed: Drive letter is "   & zPath1
       CASE %SHCNE_DRIVEREMOVED  : sMessage = "SHCNE_DRIVEREMOVED - Drive removed: Drive letter is  "  & zPath1
       CASE %SHCNE_DRIVEADD      : sMessage = "SHCNE_DRIVEADD - Drive added: Drive letter is  "        & zPath1
     END SELECT
     TextAdd(sMessage)

   CASE %WM_SIZE 'wParam = flag, LOWORD(lParam) = Width, HIWORD(lParam) = height
     IF CBWPARAM <> %SIZE_MINIMIZED THEN
       MoveWindow(GetDlgItem(hDlg, %Edit), 5, 5, LO(WORD, CBLPARAM) - 10, HI(WORD, CBLPARAM) - 10, %TRUE)
     END IF

 END SELECT

END FUNCTION
'______________________________________________________________________________

FUNCTION PBMAIN()
 LOCAL NotificationFilter AS DEV_BROADCAST_DEVICEINTERFACE
 LOCAL hDevNotify         AS DWORD
 LOCAL ulID               AS DWORD
 LOCAL ppidl              AS DWORD
 LOCAL cEntries           AS LONG

 DIALOG NEW %HWND_DESKTOP, "WM_DEVICECHANGE & SHCNE_DISKEVENTS - Insert or remove USB device or media", , , _
 500, 150, %WS_POPUP OR %WS_BORDER OR %WS_DLGFRAME OR %WS_THICKFRAME OR %WS_CAPTION OR %WS_SYSMENU OR _
 %WS_MINIMIZEBOX OR %WS_MAXIMIZEBOX OR %WS_CLIPSIBLINGS OR %WS_VISIBLE OR %DS_MODALFRAME OR %DS_3DLOOK OR _
 %DS_NOFAILCREATE OR %DS_SETFONT, %WS_EX_CONTROLPARENT OR %WS_EX_LEFT OR %WS_EX_LTRREADING OR %WS_EX_RIGHTSCROLLBAR, TO hDlg

 SetClassLong(hDlg, %GCL_HICON, ExtractIcon(GetModuleHandle(""), "Shell32.dll", 243)) 'Set a dialog icon

 CONTROL ADD TEXTBOX, hDlg, %Edit, "Insert or remove card reader media, USB device..." & $CRLF, _
 5, 5, 940, 225, %WS_CHILD OR %WS_VISIBLE OR %WS_TABSTOP OR %WS_HSCROLL OR %WS_VSCROLL OR %ES_LEFT OR _
 %ES_MULTILINE OR %ES_AUTOHSCROLL OR %ES_AUTOVSCROLL OR %ES_WANTRETURN OR %ES_NOHIDESEL, _
 %WS_EX_CLIENTEDGE OR %WS_EX_LEFT OR %WS_EX_RIGHTSCROLLBAR OR %WS_EX_LTRREADING

 'DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE are automatically broadcast for port devices.
 'Volume notifications are also broadcast to top-level windows,
 'so not necessary to RegisterDeviceNotification for DBT_DEVTYP_PORT OR DBT_DEVTYP_VOLUME OR DBT_DEVTYP_OEM
 NotificationFilter.DbDiSize = SIZEOF(DEV_BROADCAST_DEVICEINTERFACE)
 NotificationFilter.DbDiDevicetype = %DBT_DEVTYP_DEVICEINTERFACE
 hDevNotify = RegisterDeviceNotification(hDlg, BYVAL VARPTR(NotificationFilter), _
                                         %DEVICE_NOTIFY_WINDOW_HANDLE OR _
                                         %DEVICE_NOTIFY_ALL_INTERFACE_CLASSES)
 IF hDevNotify = 0 THEN
   MessageBox(%HWND_DESKTOP, "RegisterDeviceNotification failed!", "WM_DEVICECHANGE & SHCNE_DISKEVENTS", %MB_OK OR %MB_TOPMOST)
 END IF

'%DBT_DEVTYP_OEM             = &H0& 'Original Equipment Manufacturer(OEM) or Independent Hardware Vendor(IHV) defined device type. This structure is a DEV_BROADCAST_OEM structure.
'%DBT_DEVTYP_DEVNODE         = &H1& 'Devnode number (specific to Windows 95).
'%DBT_DEVTYP_VOLUME          = &H2& 'Logical volume. This structure is a DEV_BROADCAST_VOLUME structure.
'%DBT_DEVTYP_PORT            = &H3& 'Port device (serial or parallel). This structure is a DEV_BROADCAST_PORT structure.
'%DBT_DEVTYP_NET             = &H4& 'Network Resource (UNC).
'%DBT_DEVTYP_DEVICEINTERFACE = &H5& 'Class of devices. This structure is a DEV_BROADCAST_DEVICEINTERFACE structure.
'%DBT_DEVTYP_HANDLE          = &H6& 'File system handle. This structure is a DEV_BROADCAST_HANDLE structure.

 cEntries = 1
 DIM pshcne(0 TO cEntries - 1) AS SHChangeNotifyEntry  'Must be only one element when calling SHChangeNotifyRegister

 SHGetSpecialFolderLocation(hDlg, %CSIDL_DESKTOP, ppidl)
 pshcne(0).pidl = ppidl
 pshcne(0).fRecursive = %FALSE

 'For media like memory card in a card reader and also for drive like a USB stick
 ulID = SHChangeNotifyRegister(BYVAL hDlg, %SHCNRF_SHELLLEVEL, _
                               %SHCNE_DRIVEADD OR %SHCNE_DRIVEREMOVED OR _
                               %SHCNE_MEDIAINSERTED OR %SHCNE_MEDIAREMOVED, _
                               %WM_APP, BYVAL cEntries, BYVAL VARPTR(pshcne(0)))
 IF ulID = 0 THEN
   MessageBox(%HWND_DESKTOP, "SHChangeNotifyRegister failed!", "WM_DEVICECHANGE & SHCNE_DISKEVENTS", %MB_OK OR %MB_TOPMOST)
 END IF

 DIALOG SHOW MODAL hDlg, CALL DlgProc

 IF ulID THEN SHChangeNotifyDeregister(ulID) 'A dword value that specifies the registration ID returned by SHChangeNotifyRegister.
 IF hDevNotify THEN UnregisterDeviceNotification(hDevNotify)

END FUNCTION
'______________________________________________________________________________
'