Ok, so here's my design goal:
Automatically Close Portable Programs and Eject Portable Drive
I'm just using AutoIt3 out of pure laziness. It's sad when I know Delphi and C++ and rely on a scripting language to accomplish things, but it's due to the ridiculously short development time and stability of the various scripting platforms (AI3, NSIS, etc.).
So, for now, I'm just posting the code. If you want to try it yourself, be my guest, and if you are good with AutoIt3, please comment on the script.
Code:
; ----------------------------------------------------------------------------
;
; EjectUSB
; AutoIt Version: 3.0
; Language: English
; Platform: Win9x/NT
; Author: Queue
;
; Script Function:
; Closes all programs running from Flash Drive then ejects.
;
; ----------------------------------------------------------------------------
; Prepare environment
#NoTrayIcon
$name = "EjectUSB"
$version = "1.0"
$title = "Remove Flash Drive"
$sysdrive = StringLeft(@WindowsDir, 3)
; Check parameters
If $CmdLine[0] = 0 Then
$file = @ScriptDir
Else
If $CmdLine[1] = "/help" OR $CmdLine[1] = "/h" OR $CmdLine[1] = "/?" _
OR $CmdLine[1] = "-h" OR $CmdLine[1] = "-?" Then
MsgBox(64, $title, "The syntax for this utility is:" & @CRLF & $name & ".exe DriveLetterToEject")
Exit 0
Else
If FileExists($CmdLine[1]) Then
$file = $CmdLine[1]
ElseIf StringLen($CmdLine[1]) = 1 Then
If FileExists($CmdLine[1] & ":") Then
$file = $CmdLine[1] & ":\"
Else
$file = @ScriptDir
EndIf
Else
$file = @ScriptDir
EndIf
EndIf
EndIf
; Find current drive letter if not specified
If $file = @ScriptDir Then
If StringMid(@ScriptDir,2,1)=":" Then
$file = StringLeft(@ScriptDir, 2) & "\"
Else
$file = StringLeft(@ScriptDir, StringInStr(@ScriptDir, "\", 2, 3))
EndIf
EndIf
; Append backslash if missing
If StringRight($file, 1) <> "\" Then
$file &= "\"
EndIf
; Prevent system drive eject
If $file = $sysdrive OR StringInStr($file, @WindowsDir, 2) <> 0 Then
MsgBox(16, $title, "Cannot Eject the System Drive.")
Exit 0
EndIf
; Read INI settings
$timetowait = IniRead(@ScriptDir & "\" & $name & ".ini", "TimeToWait", "Seconds", "5")
$exceptions = IniReadSection(@ScriptDir & "\" & $name & ".ini", "Exceptions")
; Process exceptions
If @error = 0 Then
If $exceptions[0][0] > 0 Then
$list = ProcessList()
For $i = 1 To $list[0][0]
$loc = _ProcessGetLocation($list[$i][1])
If StringLeft($loc, StringLen($file)) = $file Then
If StringInStr($loc, @ScriptName, 2) = 0 Then
$k = 0
For $j = 1 To $exceptions[0][0]
If StringInStr($loc, $exceptions[$j][1], 2) <> 0 Then $k = 1
Next
If $k = 0 Then
If ProcessExists($list[$i][1]) Then ProcessClose($list[$i][1])
EndIf
EndIf
EndIf
Next
$list = ProcessList()
For $i = 1 To $list[0][0]
$loc = _ProcessGetLocation($list[$i][1])
If StringLeft($loc, StringLen($file)) = $file Then
If StringInStr($loc, @ScriptName, 2) = 0 Then
If ProcessExists($list[$i][1]) Then ProcessWaitClose($list[$i][1], Number($timetowait))
EndIf
EndIf
Next
EndIf
EndIf
; Terminate programs
$list = ProcessList()
For $i = 1 To $list[0][0]
$loc = _ProcessGetLocation($list[$i][1])
If StringLeft($loc, StringLen($file)) = $file Then
If StringInStr($loc, @ScriptName, 2) = 0 Then
If ProcessExists($list[$i][1]) Then ProcessClose($list[$i][1])
EndIf
EndIf
Next
_RefreshSystemTray()
; Run drive ejector
If StringLen($file) = 3 Then
If StringRight($file, 2) = ":\" Then
If FileExists(@ScriptDir & "\RemoveDrive.exe") Then
Run(@ScriptDir & "\RemoveDrive.exe " & StringLeft($file, 2))
ElseIf FileExists(@ScriptDir & "\USB_Disk_Eject.exe") Then
Run(@ScriptDir & "\USB_Disk_Eject.exe /REMOVELETTER " & StringLeft($file, 1))
EndIf
EndIf
EndIf
Exit 1
; -------------------------- Begin Custom Functions ---------------------------
Func _ProcessGetLocation($iPID)
Local $aProc = DllCall('kernel32.dll', 'hwnd', 'OpenProcess', 'int', BitOR(0x0400, 0x0010), 'int', 0, 'int', $iPID)
If $aProc[0] = 0 Then Return SetError(1, 0, '')
Local $vStruct = DllStructCreate('int[1024]')
DllCall('psapi.dll', 'int', 'EnumProcessModules', 'hwnd', $aProc[0], 'ptr', DllStructGetPtr($vStruct), 'int', DllStructGetSize($vStruct), 'int*', 0)
Local $aReturn = DllCall('psapi.dll', 'int', 'GetModuleFileNameEx', 'hwnd', $aProc[0], 'int', DllStructGetData($vStruct, 1), 'str', '', 'int', 2048)
If StringLen($aReturn[3]) = 0 Then Return SetError(2, 0, '')
Return $aReturn[3]
EndFunc
; ===================================================================
; _RefreshSystemTray($nDealy = 1000)
;
; Removes any dead icons from the notification area.
; Parameters:
; $nDelay - IN/OPTIONAL - The delay to wait for the notification area to expand with Windows XP's
; "Hide Inactive Icons" feature (In milliseconds).
; Returns:
; Sets @error on failure:
; 1 - Tray couldn't be found.
; 2 - DllCall error.
; ===================================================================
Func _RefreshSystemTray($nDelay = 1000)
; Save Opt settings
Local $oldMatchMode = Opt("WinTitleMatchMode", 4)
Local $oldChildMode = Opt("WinSearchChildren", 1)
Local $error = 0
Do; Pseudo loop
Local $hWnd = WinGetHandle("classname=TrayNotifyWnd")
If @error Then
$error = 1
ExitLoop
EndIf
Local $hControl = ControlGetHandle($hWnd, "", "Button1")
; We're on XP and the Hide Inactive Icons button is there, so expand it
If $hControl <> "" And ControlCommand($hWnd, "", $hControl, "IsVisible") Then
ControlClick($hWnd, "", $hControl)
Sleep($nDelay)
EndIf
Local $posStart = MouseGetPos()
Local $posWin = WinGetPos($hWnd)
Local $y = $posWin[1]
While $y < $posWin[3] + $posWin[1]
Local $x = $posWin[0]
While $x < $posWin[2] + $posWin[0]
DllCall("user32.dll", "int", "SetCursorPos", "int", $x, "int", $y)
If @error Then
$error = 2
ExitLoop 3; Jump out of While/While/Do
EndIf
$x = $x + 8
WEnd
$y = $y + 8
WEnd
DllCall("user32.dll", "int", "SetCursorPos", "int", $posStart[0], "int", $posStart[1])
; We're on XP so we need to hide the inactive icons again.
If $hControl <> "" And ControlCommand($hWnd, "", $hControl, "IsVisible") Then
ControlClick($hWnd, "", $hControl)
EndIf
Until 1
; Restore Opt settings
Opt("WinTitleMatchMode", $oldMatchMode)
Opt("WinSearchChildren", $oldChildMode)
SetError($error)
EndFunc; _RefreshSystemTray()
With how it's built, you can specify a filter file of programs not to force closed on its first pass; this is to let you NOT force closed registry wrappers, just kill the main programs so the registry wrappers will close on their own (and thus clean up the registry like they're supposed to).
After the first filtered pass, EjectUSB will wait for unfiltered programs to close (5 second maximum wait by default) and then force close everything before calling one of two utilities to eject the portable drive (one is listed in the Portable Freeware database, the other is available at:
www.uwe-sieber.de).
Why am I not releasing this fully yet? Well, I want to make the whole ''kill and wait'' process into a proper loop that can run indefinitely until all programs are closed for one, second, I probably need a second set of checks and waits before the drive ejection.
Also, it currently doesn't work on Win9x; I need a function that can get full path info for running programs on Win9x. Even then, the drive ejection won't work on Win9x as neither ejection utility currently supports it.
Queue
---
---
---
Alrighty, time for revision 2. It will run on Win98SE (as opposed to erroring or not starting at all), but it won't really close anything; to compensate I have it close the PStart portable error message that fires when you close PStart on Win98SE, so it's not useless.
It now also closes any explorer windows that are opened to your portable drive; this is probably WinXP only as the titlebar on explorer/folder windows is different between XP and 9x. It can potentially close other windows if their titlebar starts with the path specified.
Code:
; Utility AutoIt3 Script
;
; Prepared by Queue
;
; Last compiled with AutoIt3 version 3.2.10.0
; http://www.autoitscript.com/autoit3/
;
; Uses RefreshSystemTray version 1st Feb 2005
; http://www.autoitscript.com/forum/index.php?showtopic=7404
;
; [QM]
;
; No Copyright. No Company.
; ========================================================================================
;symbols
#NoTrayIcon
$name = "EjectUSB"
$title = "Remove Portable Drive"
;end
; ========================================================================================
;code
; Parse parameters
If $CmdLine[0] = 0 Then
; Find current drive letter if not specified
If StringMid(@ScriptDir,2,1) = ":" Then
$file = StringLeft(@ScriptDir, 2) & "\"
Else
$file = StringLeft(@ScriptDir, StringInStr(@ScriptDir, "\", 2, 3))
EndIf
Else
If $CmdLine[1] = "/help" OR $CmdLine[1] = "/h" OR $CmdLine[1] = "/?" _
OR $CmdLine[1] = "-h" OR $CmdLine[1] = "-?" Then
MsgBox(64, $title, "The syntax for this utility is:" & @CRLF & $name & ".exe DriveLetterToEject")
Exit 0
Else
If FileExists($CmdLine[1]) Then
$file = $CmdLine[1]
ElseIf StringLen($CmdLine[1]) = 1 AND FileExists($CmdLine[1] & ":\") Then
$file = $CmdLine[1] & ":\"
Else
; Find current drive letter if invalid value specified
If StringMid(@ScriptDir,2,1) = ":" Then
$file = StringLeft(@ScriptDir, 2) & "\"
Else
$file = StringLeft(@ScriptDir, StringInStr(@ScriptDir, "\", 2, 3))
EndIf
EndIf
EndIf
EndIf
; Append backslash if missing
If StringRight($file, 1) <> "\" Then
$file &= "\"
EndIf
; Prevent system drive eject
If $file = StringLeft(@WindowsDir, 3) OR StringInStr($file, @WindowsDir, 2) <> 0 Then
MsgBox(16, $title, "Cannot Eject the System Drive.")
Exit 0
EndIf
; Read INI settings
$timetowait = IniRead(@ScriptDir & "\" & $name & ".ini", "TimeToWait", "Seconds", "5")
SetError(0)
$exclusions = IniReadSection(@ScriptDir & "\" & $name & ".ini", "Exclusions")
$excerror = @error
$inclusions = IniReadSection(@ScriptDir & "\" & $name & ".ini", "Inclusions")
$incerror = @error
If $excerror = 0 Then
If $exclusions[0][0] = 0 Then $excerror = 1
EndIf
If $incerror = 0 Then
If $inclusions[0][0] = 0 Then $incerror = 1
EndIf
; Process exclusions and inclusions
$list = ProcessList()
For $i = 1 To $list[0][0]
$loc = _ProcessGetLocation($list[$i][1])
$k = 0
If StringLeft($loc, StringLen($file)) = $file Then
If StringInStr($loc, @ScriptName, 2) = 0 Then
$k = 1
If $excerror = 0 Then
For $j = 1 To $exclusions[0][0]
If StringInStr($loc, $exclusions[$j][1], 2) <> 0 Then $k = 0
Next
EndIf
EndIf
EndIf
; Process inclusions
If $incerror = 0 Then
For $j = 1 To $inclusions[0][0]
If StringInStr($loc, $inclusions[$j][1], 2) <> 0 Then $k = 1
Next
EndIf
; Terminate programs
If $k = 1 Then
If ProcessExists($list[$i][1]) Then ProcessClose($list[$i][1])
EndIf
Next
If $excerror = 0 Then
$list = ProcessList()
For $i = 1 To $list[0][0]
$loc = _ProcessGetLocation($list[$i][1])
$k = 0
If StringLeft($loc, StringLen($file)) = $file Then
If StringInStr($loc, @ScriptName, 2) = 0 Then
$k = 1
For $j = 1 To $exclusions[0][0]
If StringInStr($loc, $exclusions[$j][1], 2) <> 0 Then $k = 0
Next
EndIf
EndIf
; Process inclusions
If $incerror = 0 Then
For $j = 1 To $inclusions[0][0]
If StringInStr($loc, $inclusions[$j][1], 2) <> 0 Then $k = 1
Next
EndIf
; Wait for programs to close
If $k = 1 Then
If ProcessExists($list[$i][1]) Then ProcessWaitClose($list[$i][1], Number($timetowait))
EndIf
Next
; Terminate programs
$list = ProcessList()
For $i = 1 To $list[0][0]
$loc = _ProcessGetLocation($list[$i][1])
$k = 0
If StringLeft($loc, StringLen($file)) = $file Then
If StringInStr($loc, @ScriptName, 2) = 0 Then
$k = 1
EndIf
EndIf
; Process inclusions
If $incerror = 0 Then
For $j = 1 To $inclusions[0][0]
If StringInStr($loc, $inclusions[$j][1], 2) <> 0 Then $k = 1
Next
EndIf
; Terminate programs
If $k = 1 Then
If ProcessExists($list[$i][1]) Then ProcessClose($list[$i][1])
EndIf
Next
EndIf
; Wait for programs to close
$list = ProcessList()
For $i = 1 To $list[0][0]
$loc = _ProcessGetLocation($list[$i][1])
$k = 0
If StringLeft($loc, StringLen($file)) = $file Then
If StringInStr($loc, @ScriptName, 2) = 0 Then
$k = 1
EndIf
EndIf
; Process inclusions
If $incerror = 0 Then
For $j = 1 To $inclusions[0][0]
If StringInStr($loc, $inclusions[$j][1], 2) <> 0 Then $k = 1
Next
EndIf
; Wait for programs to close
If $k = 1 Then
If ProcessExists($list[$i][1]) Then
If ProcessWaitClose($list[$i][1], Number($timetowait)) = 0 Then
If ProcessExists($list[$i][1]) Then ProcessClose($list[$i][1])
EndIf
EndIf
EndIf
Next
; Close PStart portable Win98SE error window
WinClose("PStart portable")
; Remove dead system tray icons
_RefreshSystemTray()
; Close explorer windows that are viewing the portable drive
Opt("WinTitleMatchMode", -1)
$list = WinList($file)
For $i = 1 To $list[0][0]
WinClose($list[$i][0])
Next
; Run drive ejector
If StringLen($file) = 3 Then
If StringRight($file, 2) = ":\" Then
If FileExists(@ScriptDir & "\RemoveDrive.exe") Then
Run(@ScriptDir & "\RemoveDrive.exe " & StringLeft($file, 2), "", @SW_HIDE)
ElseIf FileExists(@ScriptDir & "\USB_Disk_Eject.exe") Then
Run(@ScriptDir & "\USB_Disk_Eject.exe /REMOVELETTER " & StringLeft($file, 1))
EndIf
EndIf
EndIf
Exit 1
; ........................................................................................
Func _ProcessGetLocation($iPID)
Local $hProc = DllCall("kernel32.dll", "int", "OpenProcess", "int", 0x0410, "int", 0, "int", $iPID)
If @error OR $hProc[0] = 0 Then
If $hProc[0] Then DllCall("kernel32.dll", "int", "CloseHandle", "int", $hProc[0])
Return SetError(1, 0, "")
EndIf
Local $stHMod = DllStructCreate("int hMod")
Local $stCB = DllStructCreate("dword cbNeeded")
Local $resEnum = DllCall("psapi.dll", "int", "EnumProcessModules", "int", $hProc[0], "ptr", DllStructGetPtr($stHMod), "dword", DllStructGetSize($stHMod), "ptr", DllStructGetPtr($stCB, 1))
If @error OR $resEnum[0] = 0 Then
DllCall("kernel32.dll", "int", "CloseHandle", "int", $hProc[0])
Return SetError(2, 0, "")
EndIf
Local $resPath = DllCall("psapi.dll", "int", "GetModuleFileNameEx", "int", $hProc[0], "int", DllStructGetData($stHMod, 1), "str", "", "dword", 32768)
DllCall("kernel32.dll", "int", "CloseHandle", "int", $hProc[0])
If @error Then
Return SetError(3, 0, "")
EndIf
SetError(0)
Return $resPath[3]
EndFunc
; ........................................................................................
; _RefreshSystemTray($nDelay = 1000)
;
; Removes any dead icons from the system tray.
; Parameters:
; $nDelay - IN/OPTIONAL - The delay to wait for the notification area to expand with
; Windows XP's "Hide Inactive Icons" feature (In milliseconds).
; Returns:
; Sets @error on failure:
; 1 - Tray couldn't be found.
; 2 - DllCall error.
; ........................................................................................
Func _RefreshSystemTray($nDelay = 1000)
; Save Opt settings
Local $oldMatchMode = Opt("WinTitleMatchMode", 4)
Local $oldChildMode = Opt("WinSearchChildren", 1)
Local $error = 0
Do; Pseudo loop
Local $hWnd = WinGetHandle("classname=TrayNotifyWnd")
If @error Then
$error = 1
ExitLoop
EndIf
Local $hControl = ControlGetHandle($hWnd, "", "Button1")
; We're on XP and the Hide Inactive Icons button is there, so expand it
If $hControl <> "" And ControlCommand($hWnd, "", $hControl, "IsVisible") Then
ControlClick($hWnd, "", $hControl)
Sleep($nDelay)
EndIf
Local $posStart = MouseGetPos()
Local $posWin = WinGetPos($hWnd)
Local $y = $posWin[1]
While $y < $posWin[3] + $posWin[1]
Local $x = $posWin[0]
While $x < $posWin[2] + $posWin[0]
DllCall("user32.dll", "int", "SetCursorPos", "int", $x, "int", $y)
If @error Then
$error = 2
ExitLoop 3; Jump out of While/While/Do
EndIf
$x = $x + 8
WEnd
$y = $y + 8
WEnd
DllCall("user32.dll", "int", "SetCursorPos", "int", $posStart[0], "int", $posStart[1])
; We're on XP so we need to hide the inactive icons again.
If $hControl <> "" And ControlCommand($hWnd, "", $hControl, "IsVisible") Then
ControlClick($hWnd, "", $hControl)
EndIf
Until 1
; Restore Opt settings
Opt("WinTitleMatchMode", $oldMatchMode)
Opt("WinSearchChildren", $oldChildMode)
SetError($error)
EndFunc; _RefreshSystemTray()
;end
Queue