Sign in to follow this  
Followers 0
Eric Bivens

Automate defrag for Windows 2000 MMC

1 post in this topic

We needed to make the Task Scheduler start DFRG.MSC to run silently in the background without user intervention or interference, whether or not a user is logged on. The problem was that the MMC worked OK on the desktop, but if you start it with the Task Scheduler, it appeard to be "blind" and you could not send keys to it by any of the normal methods I could see. After some trial and error (mostly error) I as able to get the script to interact with the MMC properly.

This script will invoke the MMC, make sure it is running, start the defrag, and enter a loop checking the status of the defrag process. If the MMC process ends, any of the expected messages appear, or it appears to hang up, it will terminate the MMC and log what it did. The writelog function is a little funky, but if you read the code & run it, you will see why I did it that way.

This only does the C: drive at the moment, but I may add the ability to enumerate & do the others in my spare time.

Please critique this, as this is my first AutoIt script.

;------------------------------------------------------------------------------------------------------------------------
;------------------------------------------------------------------------------------------------------------------------
;------------------------------------------------------------------------------------------------------------------------
;- Legal:   This script is provided "as-is" without any warranty of any kind.  If you choose to use it, you assume
;-         the risk of any consequences of its use.  If you do not agree to these terms, don't use it.  This script
;-         is released into the public domain, and you may use it freely as you see fit, provided no one is hurt, and 
;-         you buy me dinner if you make a fortune using it.  /s Eric Bivens, author.
;-
;-
;- Purpose:  This script will automate the running of the defrag process as a scheduled task on Windows 2000 machines.
;-         This script was needed because the DFRGNTFS.EXE would not take command line parms (like WinXP would), 
;-         and would only run from the MMC interactively.  This script should handle both the native Windows 
;-         version as well as the DiskKeeper (DK) version.  They differ significantly in the way they behave 
;-         and the controls they use.  If you run this interactively, you will see the windows operate; if run as
;-         a scheduled task you will see nothing.
;-
;- Approach: This script will try to determine the type of defrag in use and alter its behavior accordingly.  It does 
;-         this by looking for traits unique to each flavor of defrag and making a best-guess as to which is in use.
;-         It also looks at the list of running tasks for another instance of itself, and exits if it finds it.  
;-         Once it has its own identity, this script will loop until it finds an appropriate message that the defrag
;-         is complete, or the MMC window no longer exists.  
;-         
;- Quirks:   1) When finished, the native defrag puts up a window saying "defragmentation complete", the DK indicates its
;-            status in the message line text.
;-         2) When finished, the native defrag blanks out the message line text, so you don't really know if it is done
;-            without the "defragmentation complete" window.  I added some code to regularly check whether the "defrag"
;-            and "stop" buttons were enabled/disabled, and used this as a secondary way to detect completion.
;-         3) I tried several ways of controlling the MMC.  At first I sent keystrokes to the windows various ways.  
;-            While it worked fine interactively, if running as a scheduled task it would not work reliably.  I wound 
;-            up identifying the controls for each button and sending mouse click commands directly to them, which 
;-            seemed to work reliably.
;-         4) The writelog function is a little wierd by design.  I did not want to write a record every time it looped,
;-            so I keep track of duplicates with $DupLogLineCounter, and when it exceeds $DupLogLinesToSkip, it writes
;-            the line with a (xNN) counter indicating how many duplicates would have been written.  CAVEAT:  The last line
;-            sent is not written when you exit the program, so you have to give it a "trash" line to flush out the previous
;-            one.  You will see this in the code.
;-
;- Caveat:   If you are using a different defrag tool, or if different DiskKeeper versions have different control ID's you
;-         will have to use the AU3INFO tool to figure them out and adjust the script accordingly.
;-
;------------------------------------------------------------------------------------------------------------------------
;------------------------------------------------------------------------------------------------------------------------
;------------------------------------------------------------------------------------------------------------------------


#NoTrayIcon                                                 ; No tray icon
AutoItSetOption ("SendKeyDelay", 1000)                      ; 1 second delay between keystrokes
AutoItSetOption ("WinDetectHiddenText", 1)                  ; Include hidden text in any text searches

;- Action menu variables
$controlIDActionMenu        = 8193                          ; ID of the action menu on the MMC for defrag
$controlIDDefrag            = 4648                          ; ID of the Defrag menu element of the action menu on the MMC for defrag
;- Stop button variables
$controlIDStopButtonDK      = 4388                          ; ID of the "stop" button for DiskKeeper
$controlIDStopButtonWIN     = 8102                          ; ID of the "stop" button for native Windows defrag
$controlIDStopButtonToUse   = 0
$StopButtonTextWIN          = "Stop"                        ; Text contained in the "stop" button for native Windows defrag
$StopButtonClassNNWIN       = "Button4"                     ; Class name of the "stop" button for native Windows defrag
;- Defrag button variables
$controlIDDefragButtonDK    = 4385                          ; ID of the "Defrag" button for DiskKeeper
$controlIDDefragButtonWIN   = 8101                          ; ID of the "Defrag" button for native Windows defrag
$controlIDDefragButtonToUse = 0
$DefragButtonTextWIN        = "Defragment"                  ; Text contained in the "Defrag" button for native Windows defrag
$DefragButtonClassNNWIN     = "Button2"                     ; Class name of the "Defrag" button for native Windows defrag
;- "Defrag Complete" window info (Windows version only)
$TitleDefragCompleteWIN     = "Defragmentation Complete"    ; Text title of the of the "Defrag complete" window for native Windows defrag
$controlIDCloseComplete     = 2                             ; ID of the "Close" button in the "Defrag complete" window for native Windows defrag
;- Various constants
$FileModeRead               = 0
$FileModeWriteAppend        = 1
$FileModeWriteOver          = 2
$FileModeReadRaw            = 4
$FileModeCreateDir          = 8
$NotCaseSensitive           = 0
$StripLeadingWS             = 1
$StripTrailingWS            = 2
$StripDoubleWS              = 4
$StripAllWS                 = 8
$FiftySpaces                = "                                               "
$DupLogLinesToSkip          = 19
$SleepWaitTime              = 5000
$DebugMe                    = 0                             ; 0=none, 1=some, 2=more
$LogFilePath                = "C:\Flags"
$DefragButtonEnabledLoopLimit = 2
$NoStatusCountLoopLimit     = 10
;- Variables
$DupLogLineCounter          = 0
$DupLogLineHold             = ""
$AnotherScriptRunning       = "N"
$RC                         = 0                             ; Return code
$KeepLooping                = "Y"
$ErrorFlag                  = "N"
$NoStatusCount              = 0
$DefragButtonEnabledCount   = 0

;- Calculated stuff....
$EXEdate                    = FileGetTime(@AutoItExe, 0, 0) ; get date of this EXE into array
$Array1                     = StringSplit(@scriptname,".")  ; Split the script name into an array
$ExeBaseName                = $Array1[1]                    ; Get the first (or only) component
$ScriptName15               = StringMid(@scriptname,1,15)   ; Get first 15 characters of script name for comparison to processes.

;If ($DebugMe > 1) Then MsgBox(0, "Debug", "Starting...")

If @Compiled <> 1 Then
    MsgBox(0, "Error", "You are attempting to run an uncompiled version of the script.")
    Exit
EndIf

$logfile = FileOpen($LogFilePath & "\" & $ExeBaseName & ".Log", $FileModeWriteAppend + $FileModeCreateDir)

;If ($DebugMe > 1) Then MsgBox(0, "Debug", "Logfile open")

; Check if file opened OK
If $logfile = -1 Then
;   MsgBox(0, "Error", "Unable to open file.")
    Exit
EndIf

writelog("------------------------------------------------------------------------------")
writelog("------------------------------ Started ---------------------------------------")
writelog("---------------- Script Run Information:")
writelog("----- EXE Name = <" & @AutoItExe & ">")
writelog("----- EXE Date = <" & $EXEdate[0] & "/" & $EXEdate[1] & "/" & $EXEdate[2] & "-" & $EXEdate[3] & ":" & $EXEdate[4] & ":" & $EXEdate[5] & ">")
writelog("-- Current Dir = <" & @WorkingDir & ">")
writelog("--------- Name = <" & @ComputerName & ">")
writelog("---- OS/Ver/SP = <" & @OSType & "/" & @OSVersion & "/" & @OSServicePack & ">")
writelog("------- UserID = <" & @LogonDomain & "/" & @UserName & ">")
writelog("- User Profile = <" & @UserProfileDir & ">")
writelog("---- ProcessID = <" & @AutoItPID & ">")
writelog("-- Script Name = <" & @ScriptName & ">")
writelog("---------------- ")

If (@OSVersion <> "WIN_2000") Then
    writelog("-- ERROR:  Incorrect operating system detected.")
    writelog("-- ERROR:  OS - Required: <WIN_2000>" & " Detected: <" & @OSVersion & ">")
    writelog("-- ERROR:  Exiting...")
    writelog("------------------------------  Ended  ---------------------------------------")
    writelog("------------------------------------------------------------------------------")
    writelog("")
    writelog("Flush")                           ; This won't be written.....
    Exit 1
Else
    writelog("-- Operating System is correct version.")
EndIf


;If ($DebugMe > 1) Then MsgBox(0, "Debug", "Before doing process list")

writelog("-- Relavent Process List:")
$ProcessList = ProcessList()
for $i = 1 to $ProcessList[0][0]
    If ($ProcessList[$i][0] = $ScriptName15) Then
        if ($ProcessList[$i][1] = @AutoItPID) Then
            $ProcessList[$i][1] = $ProcessList[$i][1] & "  <<< This Process"
        Else
            $ProcessList[$i][1] = $ProcessList[$i][1] & "  <<< Alien"
            $AnotherScriptRunning = "Y"
        EndIf
    writelog("--         " & StringMid($ProcessList[$i][0] & $FiftySpaces,1,20) & $ProcessList[$i][1])
    EndIf
next

;If ($DebugMe > 1) Then MsgBox(0, "Debug", "After doing process list")

If ($AnotherScriptRunning = "Y") Then
    writelog("-- ERROR:  Another instance of this script is running (see process list above).")
    writelog("-- ERROR:  I will terminate...")
    writelog("------------------------------  Ended  ---------------------------------------")
    writelog("------------------------------------------------------------------------------")
    writelog("")
    writelog("Flush")                           ; This won't be written.....
    Exit 1
Else
    writelog("--         No other instances of this program were detected.")
EndIf

writelog("-- Preparing to start DFRG.MSC")
$RC = Run(@SystemDir & "\mmc.exe " & @SystemDir & "\dfrg.msc")   ;;;; --- maybe later..... ,"",@SW_MINIMIZE)
writelog("--         ProcessID = <" & $rc & ">")

writelog("-- Sleeping 1 to allow it to initialize....")
sleep(1000)
writelog("--         Woke up")

writelog("-- Fetching defrag window handle")
$defraghandle = WinGetHandle("Disk Defragmenter", "")
writelog("--         handle = <" & $defraghandle & ">")

writelog("-- Testing if window exists")
if winexists($defraghandle) then
    writelog("--         It does exist")
else
    writelog("--         It does NOT exist")
endif

;- Try to figure out which version of defrag is running.
writelog("-- Fetching initial status line text")
$StatusBarText=StringStripWS(StatusbarGetText($defraghandle,""),$StripLeadingWS + $StripTrailingWS)
writelog("--         text = <" & $StatusBarText & ">")

writelog("-- Fetching text from control for DiskKeeper")
$DKButtonText   = ControlGetText($defraghandle,"",$controlIDDefragButtonDK)
writelog("--         text = <" & $DKButtonText & ">")

writelog("-- Fetching text from control for Windows Defrag")
$WINButtonText  = ControlGetText($defraghandle,"",$controlIDDefragButtonWIN)
writelog("--         text = <" & $WINButtonText & ">")

writelog("-- Attempting to set focus to control button for DiskKeeper")
$DKDefragButtonRC   = ControlFocus($defraghandle,"",$controlIDDefragButtonDK)
writelog("--         RC = <" & $DKDefragButtonRC & ">")
    
writelog("-- Attempting to set focus to control button for Windows Defrag")
$WINDefragButtonRC  = ControlFocus($defraghandle,"",$controlIDDefragButtonWIN)
writelog("--         RC = <" & $WINDefragButtonRC & ">")



Select
    Case $DKDefragButtonRC  = 1
        writelog("-- It is DiskKeeper defrag, not native Windows")
        $controlIDDefragButtonToUse = $controlIDDefragButtonDK
        $controlIDStopButtonToUse   = $controlIDStopButtonDK
    Case $WINDefragButtonRC = 1
        writelog("-- It is native Windows defrag, not DiskKeeper")
        $controlIDDefragButtonToUse = $controlIDDefragButtonWIN
        $controlIDStopButtonToUse   = $controlIDStopButtonWIN
    Case Else
        writelog("-- It is Unknown")
        $controlIDDefragButtonToUse=0
EndSelect
If ($controlIDDefragButtonToUse<>0) Then
    writelog("-- Setting focus to the control button we found")
    $RC=ControlFocus($defraghandle,"",$controlIDDefragButtonToUse)
    writelog("--         RC = <" & $rc & ">")
    writelog("-- Sending click to ""defrag"" button to start defrag")
    $RC = ControlClick($defraghandle,"",$controlIDDefragButtonToUse, "left", 1)         ; Click left mouse button one time
    writelog("--         RC = <" & $rc & ">")
Else
    writelog("-- No valid button control found; cannot proceed....")
    $KeepLooping = "N"
    $ErrorFlag = "Y"
EndIf

writelog("-- Sleeping 5 to let it get started....")
sleep(5000)
writelog("--         Woke up")

writelog("---   Defrag processing loop starting...")

while $KeepLooping = "Y"
    if WinGetHandle("Disk Defragmenter", "") = $defraghandle Then
        $StatusBarText          = StringStripWS(StatusbarGetText($defraghandle,""),$StripLeadingWS + $StripTrailingWS)
        $StatusOfDefragButton   = ControlCommand($defraghandle,"",$controlIDDefragButtonToUse, "IsEnabled", "")
        $StatusOfStopButton     = ControlCommand($defraghandle,"",$controlIDStopButtonToUse,   "IsEnabled", "")
    ;writelog("--- Defrag button status = <" & $StatusOfDefragButton & ">   Stop button status = <" & $StatusOfStopButton & ">")
        Select
            Case (WinExists($TitleDefragCompleteWIN) > 0)                                   ;--- Check for defrag complete window before anything.
                writelog("---   Found ""Defragmentation Complete"" window.")
                writelog("---   Fetching handle for ""Defragmentation Complete"" window.")
                $DefragCompletehandle = WinGetHandle($TitleDefragCompleteWIN, "")                   ; Fetch handle for window title we expect
                writelog("---    handle = <" & $DefragCompletehandle & ">")
                writelog("---   Fetching text from ""close"" control for ""Defrag Complete"" window")
                $WINButtonText  = ControlGetText($DefragCompletehandle,"",$controlIDCloseComplete)  ; Fetch text from "close" button to make sure...
                writelog("---      text = <" & $WINButtonText & ">")
                writelog("--- Setting focus to ""close"" control for ""Defrag Complete"" window")
                $WINCloseButtonRC   = ControlFocus($DefragCompletehandle,"",$controlIDCloseComplete)
                writelog("---        RC = <" & $WINCloseButtonRC & ">")
                writelog("--- Sending click to ""close"" control for ""Defrag Complete"" window")
                $RC=ControlClick($DefragCompletehandle,"",$controlIDCloseComplete,"left",1)         ; Click left mouse button one time to close it.
                writelog("---        RC = <" & $rc & ">")
                writelog("---         Sleeping 2 to let things settle down...")
                sleep(2000)
                $KeepLooping = "N"
            Case ($StatusOfDefragButton = 1) and ($StatusOfStopButton = 0)                  ;--- See if defrag button enabled (it's finished)
                If ($DefragButtonEnabledCount > $DefragButtonEnabledLoopLimit) Then
                        writelog("---   Still found ""Defrag"" button enabled, ""Stop"" button disabled; assume it is finished...")
                        $KeepLooping = "N"
                Else
                    writelog("---   Found ""Defrag"" button enabled, ""Stop"" button disabled; sleep a while..")
                    $DefragButtonEnabledCount = $DefragButtonEnabledCount + 1
                EndIf
            Case ($StatusOfDefragButton = 0) and ($StatusOfStopButton = 1)                  ;--- See if defrag button disabled (it's still defragging)
                writelog("---   Still defragmenting, ""Defrag"" button disabled, ""Stop"" button enabled.")
                $NoStatusCount  = 0
            Case Stringlen($StatusBarText) = 0                                              ;--- Only check this after checking the buttons.
                If ($NoStatusCount > 10) Then
                        writelog("---   Still null status bar text; assume it is finished...")
                        $KeepLooping = "N"
                Else
                    writelog("---   Null status bar text, so we sleep a while to see if it is really done...")
                    $NoStatusCount = $NoStatusCount + 1
                EndIf
            Case StringInStr($StatusBarText, "defragmenting", $NotCaseSensitive) > 0        ;--- These can be checked if the buttons can't be found
                writelog("---   Still defragmenting, StatusText=<" & $StatusBarText & ">")
                $NoStatusCount  = 0
            Case StringInStr($StatusBarText, "scan", $NotCaseSensitive) > 0
                writelog("---   Still scanning, StatusText=<" & $StatusBarText & ">")
                $NoStatusCount  = 0
            Case StringInStr($StatusBarText, "search", $NotCaseSensitive) > 0
                writelog("---   Still searching, StatusText=<" & $StatusBarText & ">")
                $NoStatusCount  = 0
            Case StringInStr($StatusBarText, "stopped", $NotCaseSensitive) > 0
                writelog("---   Stopped defragmenting, StatusText=<" & $StatusBarText & ">")
                $KeepLooping = "N"
            Case StringInStr($StatusBarText, "defragmented", $NotCaseSensitive) > 0
                writelog("---   Finished defragmenting, StatusText=<" & $StatusBarText & ">")
                $KeepLooping = "N"
            Case StringInStr($StatusBarText, "analyzing", $NotCaseSensitive) > 0
                writelog("---   Still analyzing, StatusText=<" & $StatusBarText & ">")
                $NoStatusCount  = 0
            Case StringInStr($StatusBarText, "analyzed", $NotCaseSensitive) > 0
                writelog("---   Waiting at Analyzed, StatusText=<" & $StatusBarText & ">")
                $NoStatusCount  = 0
            Case Else
                writelog("---   Unanticipated status, StatusText=<" & $StatusBarText & ">")
                If ($NoStatusCount > $NoStatusCountLoopLimit) Then
                        $KeepLooping = "N"
                        $ErrorFlag = "Y"
                Else
                    $NoStatusCount = $NoStatusCount + 1
                EndIf
        EndSelect
        If ($KeepLooping = "Y") Then sleep($SleepWaitTime)
    Else
        $KeepLooping = "N"
        $ErrorFlag = "Y"
        writelog("-- ERROR:  Defrag ended unexpectadly!!!!!")
        writelog("-- ERROR:  WinGetHandle=" & WinGetHandle("Disk Defragmenter", "") & "  Previous handle was " & $defraghandle)
    EndIf
WEnd
if ($ErrorFlag = "Y") Then
    writelog("-- ERROR:  ")
    writelog("-- ERROR:  An error occurred.  Check previous messages to determine its cause")
    writelog("-- ERROR:  ")
EndIf

if winexists($defraghandle) then
    writelog("-- Defrag window still exists")
    writelog("-- Sending close event to window")
    $RC = WinClose($defraghandle)
    writelog("--         RC = <" & $DKDefragButtonRC & ">")
else
    writelog("-- Defrag window does NOT exist")
endif

writelog("-- Sleeping 2 to let things settle down...")
sleep(2000)
writelog("--         Woke up")

if winexists($defraghandle) then
    writelog("-- Houston, we have a problem......")
else
    writelog("-- Window was closed")
endif

writelog("------------------------------  Ended  ---------------------------------------")
writelog("------------------------------------------------------------------------------")
writelog("")
writelog("Flush")                           ; This won't be written.....

exit

;-------------------------------------------------------
;--- Write a log record.
;--- If it is identical to the previous one, only write every $DupLogLinesToSkip lines.
;--- It only writes the previous record, so you have to flush it out by always writing a different record 
;--- at the end of the program run, or you will lose the last line.
;
func    writelog($msg)
    If ($DebugMe > 2) Then FileWriteLine ($logfile, "+++Message:<" & $msg & ">")
    If ($DebugMe > 2) Then FileWriteLine ($logfile, "+++   Hold:<" & $DupLogLineHold & ">")
    $s1=@year&"/"&@mon&"/"&@mday&"-"&@hour&":"&@min&":"&@sec&" "                ;--- Prime the message line
    $RepeatedIndicator = "    "                                             ;--- Pad spaces to make it look purty...
    if ($DupLogLineHold = $msg) Then                                            ;--- If it's the same,
        $DupLogLineCounter = $DupLogLineCounter + 1                             ;---    just increment the counter
        if ($DupLogLineCounter>$DupLogLinesToSkip) Then                         ;--- If it exceeds the limit,
            $RepeatedIndicator = StringFormat("(x%2u) ",$DupLogLineCounter)     ;---    Build the repeat count
            FileWriteLine ($logfile, $s1 & $RepeatedIndicator & $msg)           ;---    Write the info
            $DupLogLineCounter = 0                                              ;---    and reset the counter
        EndIf
    Else                                                                        ;--- otherwise, if it's different
        if ($DupLogLineCounter > 0) Then                                        ;---   If there was more than one of the cached lines
            $RepeatedIndicator = StringFormat("(x%2u) ",$DupLogLineCounter+1)   ;---      Build the repeat count
        EndIf
        FileWriteLine ($logfile, $s1 & $RepeatedIndicator & $DupLogLineHold )   ;--- Now write it,
        $DupLogLineCounter = 0                                                  ;--- Reset the counter,
        $DupLogLineHold = $msg                                                  ;--- And save the comparison string to the current message
    EndIf   
    Return 0
EndFunc

;-------------------------------------------------------
;--- Return current date/time string
;
func    yyyymmddhhmmss()
    Return @year & "/" & @mon & "/" & @mday & "-" & @hour & ":" & @min & ":" & @sec 
EndFunc

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