DescriptionSubmits either all processes minus exclusions or specific processes to the EmptyWorkingSet API. This API tells Windows to purge the unused memory from an application.
Features- On-screen log that includes the date and time as well as the process names that were submitted to EmptyWorkingSet
- All settings saved to ini file in the scripts directory
- User definable interval for automating process cleanup
- All processes with exclusions can be cleaned or just specific processes
- Able to be hidden to the system tray
- Can be set to automatically start cleanup when launched and to start hidden in the system tray
- Double-click on tray icon to restore from the tray
Instructions1 - Push "Start"
2 - ???
3 - Enjoy? ...
Notes- The inspiration for this script came from a tool I found called CleanMem. I hated relying on the scheduler service to run the tool so I made my own.
- The only thing I would like to do further with this script is get it to clear the system cache in a similar manner to how the sysinternals CacheSet tool and CleanMem do. Any help doing this would be GREATLY appreciated.
- Im currently running this tool on a couple SQL machines at work that were strapped for memory (98% of 4GB) because of poor code. They are all running on a 1 hour interval for cleanup and use less than 50% of that same 4GB at any time.
- Any time the settings are changed you need to press "Start" again to apply the new settings.
Download links
As always, any tips, suggestions, or constructive criticism is not only welcomed, but encouraged!
Code:
; =============== Auto-execute ===============
#NoEnv
SendMode, Input
SetWorkingDir, %A_ScriptDir%
SetTitleMatchMode, 3
DetectHiddenWindows, On
IfExist, ram.ico
Menu, Tray, Icon, ram.ico
Menu, Tray, NoStandard
Menu, Tray, Add, Show Window, ShowWindow
Menu, Tray, Add, Exit, GuiClose
Menu, Tray, Default, Show Window
; =============== Variables ===============
Version = 1.1.0
; =============== GUI ===============
GoSub, ReadINI
Gui, Add, Button, x15 y395 w80 h30, Start
Gui, Add, Button, x175 y395 w70 h30, Hide
Gui, Add, Button, x255 y395 w70 h30, Exit
Gui, Add, Tab, x0 y5 w340 h385, Status|Settings|About
Gui, Tab, Status
Gui, Add, Edit, x20 y60 w300 h300 +ReadOnly -Wrap
Gui, Tab, Settings
Gui, Font, S12 Bold,
Gui, Add, Text, x130 y30, Interval
Gui, Add, Text, x135 y90, Type
Gui, Add, Text, x130 y280, Startup
Gui, Font,,
Gui, Add, Edit, x110 y60 w100 h20 vRunInterval, %RunInterval%
Gui, Add, Text, x+10 y60 w100 h30, min
Gui, Add, Radio, vRunType x10 y120, All processes (Exclusions)
Gui, Add, Radio, x170 y120, Specific process (Inclusions)
Gui, Add, Edit, vExclusions x15 y150 w150 h100 -Wrap, %Exclusions%
Gui, Add, Edit, vInclusions x175 y150 w150 h100 -Wrap, %Inclusions%
Gui, Add, Checkbox, vAutorun Checked%Autorun% x90 y310, Auto-run
Gui, Add, Checkbox, vHide Checked%Hide% x180 y310, Start hidden
Gui, Add, Button, x120 y355, Save Settings
Gui, Tab, About
Gui, Font, S14
Gui, Add, Text, x36 y100 w250 h150 , Written by:`n`nMatthew Giffin`nstrictlyfocused02@gmail.com
Gui, Font,,
Gui, Show, h430 w340, Memory Cleanup v%Version%
If RunType = 1
Control, Check,, Button4, Memory Cleanup v%Version%
If RunType = 2
Control, Check,, Button5, Memory Cleanup v%Version%
If Hide = 1
GoSub, ButtonHide
If Autorun = 1
GoSub, ButtonStart
Return
; =============== GUI Sub-routines ===============
ButtonExit:
GuiClose:
ExitApp
GuiSize:
If A_EventInfo = 1
WinHide, Memory Cleanup v%Version%
Return
ButtonHide:
WinHide, Memory Cleanup v%Version%
Return
ShowWindow:
WinShow, Memory Cleanup v%Version%
WinWait, Memory Cleanup v%Version%
WinActivate, Memory Cleanup v%Version%
Return
ButtonStart:
Gui, Submit, NoHide
GoSub, MemoryCleanup
GoSub, SetInterval
Return
SetInterval:
AHKRunInterval := RunInterval * 60000
SetTimer, MemoryCleanup, %AHKRunInterval%
Return
ReadINI:
IfExist, settings.ini
{
IniRead, RunInterval, settings.ini, Interval, Interval
IniRead, RunType, settings.ini, Type, RunType
IniRead, Exclusions, settings.ini, Type, Exclusions
IniRead, Inclusions, settings.ini, Type, Inclusions
IniRead, Autorun, settings.ini, Launch, Autorun
IniRead, Hide, settings.ini, Launch, Hide
If not Exclusions
Exclusions = One process name per line
If Exclusions
StringReplace, Exclusions, Exclusions, `,, `n, All
If not Inclusions
Inclusions = One process name per line
If Inclusions
StringReplace, Inclusions, Inclusions, `,, `n, All
Return
}
IfNotExist, settings.ini
{
RunInterval = 30
RunType = 1
Exclusions = One process name per line
Inclusions = One process name per line
Autorun = 0
Hide = 0
Return
}
ButtonSaveSettings:
Gui, Submit, NoHide
If Exclusions = One process name per line
Exclusions =
If Inclusions = One process name per line
Inclusions =
StringReplace, Exclusions, Exclusions, `n, `,, All
StringReplace, Inclusions, Inclusions, `n, `,, All
IniWrite, %RunInterval%, settings.ini, Interval, Interval
IniWrite, %RunType%, settings.ini, Type, RunType
IniWrite, %Exclusions%, settings.ini, Type, Exclusions
IniWrite, %Inclusions%, settings.ini, Type, Inclusions
IniWrite, %Autorun%, settings.ini, Launch, Autorun
IniWrite, %Hide%, settings.ini, Launch, Hide
MsgBox, Settings saved!`n`nTip: Press "Start" now to apply these settings without having to restart the application.
Return
; =============== Bread and Butter ===============
MemoryCleanup:
ProcList := CMDret("tasklist /fo CSV") ;Send "tasklist" to cmdret
Loop, Parse, ProcList, `n
{
If A_Index < 4 ;The first 4 lines are always junk\column|row seperators\blank
Continue
StringSplit, ProcArray, A_LoopField, `, ;Split apart using a comma delimeter
StringReplace, ProcArray1, ProcArray1, `",, 1 ;Process Name
StringReplace, ProcArray2, ProcArray2, `",, 1 ;PID
If RunType = 1
{
StringReplace, Exclusions, Exclusions, `n, `,, All
If ProcArray1 in %Exclusions%
Continue
Else
{
GO := CleanMemory(ProcArray2)
If GO = 1 ;Function returned 1 (i.e. success)
{
ControlGetText, OnScrLog, Edit1, Memory Cleanup v%Version%
ControlSetText, Edit1, %OnScrLog%`r`n`r`n%A_MMM%-%A_DD%-%A_YYYY% %A_Hour%:%A_Min%:%A_Sec%:%A_MSec% - Cleaned "%ProcArray1%", Memory Cleanup v%Version%
SendMessage, 0x115, 7, 0, Edit1, Memory Cleanup v%Version%
}
}
}
If RunType = 2
{
StringReplace, Inclusions, Inclusions, `n, `,, All
If ProcArray1 in %Inclusions%
{
GO := CleanMemory(ProcArray2)
If GO = 1 ;Function returned 1 (i.e. success)
{
ControlGetText, OnScrLog, Edit1, Memory Cleanup v%Version%
ControlSetText, Edit1, %OnScrLog%`r`n`r`n%A_MMM%-%A_DD%-%A_YYYY% %A_Hour%:%A_Min%:%A_Sec%:%A_MSec% - Cleaned "%ProcArray1%", Memory Cleanup v%Version%
SendMessage, 0x115, 7, 0, Edit1, Memory Cleanup v%Version%
}
}
}
}
Return
; =============== Functions ===============
;A function that retreives the process handle from a PID and sends it to the EmptyWorkingSet API. Returns 1 for success, NULL (blank) on failure
CleanMemory(PID) ;Written with help from "Temp01" on the AHK IRC chat (thank you again, temp01!!!)
{
Process, Exist ;Sets ErrorLevel to the PID of this running script
h := DllCall("OpenProcess", "UInt", 0x0400, "Int", false, "UInt", ErrorLevel) ;Get the handle of this script with PROCESS_QUERY_INFORMATION (0x0400)
DllCall("Advapi32.dll\OpenProcessToken", "UInt", h, "UInt", 32, "UIntP", t) ;Open an adjustable access token with this process (TOKEN_ADJUST_PRIVILEGES = 32)
VarSetCapacity(ti, 16, 0) ;Structure of privileges
NumPut(1, ti, 0) ;One entry in the privileges array...
DllCall("Advapi32.dll\LookupPrivilegeValueA", "UInt", 0, "Str", "SeDebugPrivilege", "Int64P", luid) ;Retrieves the locally unique identifier of the debug privilege:
NumPut(luid, ti, 4, "int64")
NumPut(2, ti, 12) ;Enable this privilege: SE_PRIVILEGE_ENABLED = 2
DllCall("Advapi32.dll\AdjustTokenPrivileges", "UInt", t, "Int", false, "UInt", &ti, "UInt", 0, "UInt", 0, "UInt", 0) ;Update the privileges of this process with the new access token:
DllCall("CloseHandle", "UInt", h) ;Close this process handle to save memory
hModule := DllCall("LoadLibrary", "Str", "Psapi.dll") ;Increase performance by preloading the libaray
h := DllCall("OpenProcess", "UInt", 0x400|0x100, "Int", false, "UInt", pid) ;Open process with: PROCESS_QUERY_INFORMATION (0x0400) | PROCESS_SET_QUOTA (0x100)
e := DllCall("psapi.dll\EmptyWorkingSet", "UInt", h)
DllCall("CloseHandle", "UInt", h) ;Close process handle to save memory
DllCall("FreeLibrary", "UInt", hModule) ;Unload the library to free memory
Return e
}
;CMDret can be used to retrieve and store output from console programs in a variable without displaying the console window
CMDret(CMDin, WorkingDir=0) ;by corrupt (http://www.autohotkey.com/forum/topic8606.html)
{
Global cmdretPID
tcWrk := WorkingDir=0 ? "Int" : "Str"
idltm := A_TickCount + 20
CMsize = 1
VarSetCapacity(CMDout, 1, 32)
VarSetCapacity(sui,68, 0)
VarSetCapacity(pi, 16, 0)
VarSetCapacity(pa, 12, 0)
Loop, 4 {
DllCall("RtlFillMemory", UInt,&pa+A_Index-1, UInt,1, UChar,12 >> 8*A_Index-8)
DllCall("RtlFillMemory", UInt,&pa+8+A_Index-1, UInt,1, UChar,1 >> 8*A_Index-8)
}
IF (DllCall("CreatePipe", "UInt*",hRead, "UInt*",hWrite, "UInt",&pa, "Int",0) <> 0) {
Loop, 4
DllCall("RtlFillMemory", UInt,&sui+A_Index-1, UInt,1, UChar,68 >> 8*A_Index-8)
DllCall("GetStartupInfo", "UInt", &sui)
Loop, 4 {
DllCall("RtlFillMemory", UInt,&sui+44+A_Index-1, UInt,1, UChar,257 >> 8*A_Index-8)
DllCall("RtlFillMemory", UInt,&sui+60+A_Index-1, UInt,1, UChar,hWrite >> 8*A_Index-8)
DllCall("RtlFillMemory", UInt,&sui+64+A_Index-1, UInt,1, UChar,hWrite >> 8*A_Index-8)
DllCall("RtlFillMemory", UInt,&sui+48+A_Index-1, UInt,1, UChar,0 >> 8*A_Index-8)
}
IF (DllCall("CreateProcess", Int,0, Str,CMDin, Int,0, Int,0, Int,1, "UInt",0, Int,0, tcWrk, WorkingDir, UInt,&sui, UInt,&pi) <> 0) {
Loop, 4
cmdretPID += *(&pi+8+A_Index-1) << 8*A_Index-8
Loop {
idltm2 := A_TickCount - idltm
If (idltm2 < 10) {
DllCall("Sleep", Int, 10)
Continue
}
IF (DllCall("PeekNamedPipe", "uint", hRead, "uint", 0, "uint", 0, "uint", 0, "uint*", bSize, "uint", 0 ) <> 0 ) {
Process, Exist, %cmdretPID%
IF (ErrorLevel OR bSize > 0) {
IF (bSize > 0) {
VarSetCapacity(lpBuffer, bSize+1)
IF (DllCall("ReadFile", "UInt",hRead, "Str", lpBuffer, "Int",bSize, "UInt*",bRead, "Int",0) > 0) {
IF (bRead > 0) {
TRead += bRead
VarSetCapacity(CMcpy, (bRead+CMsize+1), 0)
CMcpy = a
DllCall("RtlMoveMemory", "UInt", &CMcpy, "UInt", &CMDout, "Int", CMsize)
DllCall("RtlMoveMemory", "UInt", &CMcpy+CMsize, "UInt", &lpBuffer, "Int", bRead)
CMsize += bRead
VarSetCapacity(CMDout, (CMsize + 1), 0)
CMDout=a
DllCall("RtlMoveMemory", "UInt", &CMDout, "UInt", &CMcpy, "Int", CMsize)
VarSetCapacity(CMDout, -1) ; fix required by change in autohotkey v1.0.44.14
}
}
}
}
ELSE
break
}
ELSE
break
idltm := A_TickCount
}
cmdretPID=
DllCall("CloseHandle", UInt, hWrite)
DllCall("CloseHandle", UInt, hRead)
}
}
IF (StrLen(CMDout) < TRead) {
VarSetCapacity(CMcpy, TRead, 32)
TRead2 = %TRead%
Loop {
DllCall("RtlZeroMemory", "UInt", &CMcpy, Int, TRead)
NULLptr := StrLen(CMDout)
cpsize := Tread - NULLptr
DllCall("RtlMoveMemory", "UInt", &CMcpy, "UInt", (&CMDout + NULLptr + 2), "Int", (cpsize - 1))
DllCall("RtlZeroMemory", "UInt", (&CMDout + NULLptr), Int, cpsize)
DllCall("RtlMoveMemory", "UInt", (&CMDout + NULLptr), "UInt", &CMcpy, "Int", cpsize)
TRead2 --
IF (StrLen(CMDout) > TRead2)
break
}
}
StringTrimLeft, CMDout, CMDout, 1
Return, CMDout
}
/*
>>>>CHANGELOG<<<<
1.0 (12-23-2009)
- Original script
1.1 (10-24-2009)
- Removed need for empty.exe
-- Added CleanMemory function which uses the same API (EmptyWorkingSet) found in empty.exe