Jump to content

Recommended Posts

Posted (edited)

New version 0.0.0.31 in last post.

  • Fixed persistence not working correctly.
  • Added message box for persistence with Yes or No option. Again, clicking one adds it to the White or Blacklist files.

Usage at 8.3MB RAM

Edited by sl23
Posted

Ok I have an issue wrt the checks. I got a new alert about modified entry, but for some reason it was a long list of mods! So long that it went off the bottom of my screen! What concerns me is that first I couldn't click Yes or No as they were offscreen, but mainly and most importantly, what the hell was it? How do I know if that sort of thing is normal and that I should allow it? That's a lot of mods to allow OR deny! Must've been around 60 or more onscreen, with more below the screen. Some were drivers, some were registry, but I had to end the task as I couldn't shutdown the app.

This was why I planned to keep the app simple, I'm getting into unknown territory, besides the programming I mean! I literally don't know whether those mods were safe or not. Was it windows update or something?

Posted

lol, good luck with that :lol:

You'll see the master of the a database ( SQLite, one guy ) or the master of an editor ( Notepad++, one guy ), etc.
That one guy, is into it ( whatever that is ) like no one else and, we go and use those.

For you to get into what is your curiosity you'll end up becoming a master at it because there is no way around it.
And to code it in AutoIt, you'll benefit from forking, and IPC, and hooking. But the title is "Learning using AI to create script" and that is what got you here. :D

I find AutoIt beautiful, but I know how much, and how to squeeze it, to get the most of it. Yet there are those that are better than me. ( No low self esteem, just a fact ) 
You came with an "AI squeezer" and I guess you'll end up teaching the AI or, you'll learn yourself. :)

All in all is a good experience for you because you really wanna have that, and that will guide you ( your drive ( if you stay driven )).

So, back to coding.
I'd make a main script as orchestrator and others to collect the info and give it to the orchestrator and a separate GUI with a listview with checkmarks to white list or black list. That way the orchestrator can keep on receiving events and the GUI does not slow down anything. Even the tray icon would be it's own script that would communicate with the orchestrator/main script.
And yes, it may be overengineering, but is as non-blocking as possible. And you can close the Tray or the GUI and you can load them back again without loosing track of what is going on.
Also, everything would be noted in SQLite.

There. That is my approach to your project.
Based on that I'd tell you to play around and build experience with SQLite, to find it easy to use. That in the end, is far better than ini files.

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

  • sl23 changed the title to Learning using AI to create - StartupMonitor64
Posted (edited)

Added GUI with checkboxes. Buttons for Select All, Unselect All, Export list (as INI), OK, Cancel. Single alerts still use the old message box format for simplicity.
Fixed repeating alerts for same entries.
All created files are now stored within the "Data" folder.

Source now consists of two AutoIT files: StartupMonitor64.au3 and GUI.au3

StartupMonitor.au3:
 

;*****************************************
; StartupMonitor64.au3 by sl23
; Created with ISN AutoIt Studio v1.16
; Compiled with AutoIt v3.3.16.1
;*****************************************
#AutoIt3Wrapper_icon=StartupMonitor.ico
#AutoIt3Wrapper_Res_Fileversion_First_Increment=Y   ; AutoIncrement: Before (Y); After (N) compile. Default=N
#AutoIt3Wrapper_Res_FileVersion_AutoIncrement=y
#AutoIt3Wrapper_Res_Fileversion=0.0.0.42
#AutoIt3Wrapper_Res_ProductVersion=3.3.16.1
#AutoIt3Wrapper_Res_Description=StartupMonitor64
#AutoIt3Wrapper_Res_LegalCopyright=sl23

; Generated by GitHub Copilot AI:

#RequireAdmin
#include <MsgBoxConstants.au3>
#include <File.au3>
#include <TrayConstants.au3>
#include <Date.au3>
#include <Array.au3>
#include "GUI.au3"

; ----------------------------------------------------------------------
; Single Instance check and alert
; ----------------------------------------------------------------------
SingleInstanceForce()
Func SingleInstanceForce()
    Local $instance = "[TITLE:" & @ScriptFullPath & ";CLASS:AutoIt v3;]"
    If WinExists($instance) Then
        MsgBox($MB_ICONERROR, "StartupMonitor64", "The application is already running." & @CRLF & "Check the tray or Task Manager." & @CRLF & @CRLF & "Click OK to close new instance...")
        Exit 4
    EndIf
    AutoItWinSetTitle(@ScriptFullPath)
EndFunc

; ----------------------------------------------------------------------
; Settings and File Paths
; ----------------------------------------------------------------------
DirCreate(@ScriptDir & "\Data")
Global Const $g_settingsFile = @ScriptDir & "\Data\Settings.ini"
Global Const $g_logFile = @ScriptDir & "\Data\Log.ini"
Global Const $g_whitelistFile = @ScriptDir & "\Data\Whitelist.ini"
Global Const $g_blacklistFile = @ScriptDir & "\Data\Blacklist.ini"
Global Const $g_initialStartupFile = @ScriptDir & "\Data\InitialStartupEntries.ini"
Global Const $g_initialTasksFile = @ScriptDir & "\Data\InitialScheduledTasks.ini"

If Not DirCreate(@ScriptDir & "\Data") Then
    MsgBox(16, "Error", "Could not create Data directory!")
EndIf

; ----------------------------------------------------------------------
; Create Settings File With Comments if Needed (add GUI window size settings)
; ----------------------------------------------------------------------
If Not FileExists($g_settingsFile) Then
    Local $hSet = FileOpen($g_settingsFile, $FO_OVERWRITE)
    If $hSet <> -1 Then
        FileWriteLine($hSet, "[Options]")
        FileWriteLine($hSet, "; Show tray icon in the notification area: Show (1), Hide (0)")
        FileWriteLine($hSet, "ShowTrayIcon=1")
        FileWriteLine($hSet, "; New log file on start: New (1), Keep old log (0). Entries older than 30 days will be deleted.")
        FileWriteLine($hSet, "ClearLogOnStart=0")
        FileWriteLine($hSet, "; Time in milliseconds between monitoring checks. (Minimum 1500ms / Default 3000).")
        FileWriteLine($hSet, "MonitorTime=3000")
        FileWriteLine($hSet, "; Show messagebox for auto-removed blacklist items: Enable (1), Disable (0)")
        FileWriteLine($hSet, "ShowBlacklistMsgBox=1")
        FileWriteLine($hSet, "; Save/load persistent baseline snapshot of startup entries and scheduled tasks: Enable (1), Disable (0)")
        FileWriteLine($hSet, "PersistentBaseline=1")
        FileWriteLine($hSet, "; Monitor scheduled tasks: Enable (1), Disable (0)")
        FileWriteLine($hSet, "MonitorTasks=1")
        FileWriteLine($hSet, "")
        FileWriteLine($hSet, "[GUI]")
        FileWriteLine($hSet, "; Review Window Width (pixels). Default 500.")
        FileWriteLine($hSet, "ReviewWindowWidth=500")
        FileWriteLine($hSet, "; Review Window Height (pixels). Default 400.")
        FileWriteLine($hSet, "ReviewWindowHeight=400")
        FileWriteLine($hSet, "")
        FileWriteLine($hSet, "[ExtraStartupChecks]")
        FileWriteLine($hSet, "; Enable (1) or disable (0) each extra startup location.")
        FileWriteLine($hSet, "HKCU_RunOnce=1")
        FileWriteLine($hSet, "HKLM_RunOnce=1")
        FileWriteLine($hSet, "HKCU_Explorer_UserShellFolders=1")
        FileWriteLine($hSet, "HKCU_Explorer_ShellFolders=1")
        FileWriteLine($hSet, "HKLM_Explorer_ShellFolders=1")
        FileWriteLine($hSet, "HKLM_Explorer_UserShellFolders=1")
        FileWriteLine($hSet, "HKLM_RunServicesOnce=1")
        FileWriteLine($hSet, "HKCU_RunServicesOnce=1")
        FileWriteLine($hSet, "HKLM_RunServices=1")
        FileWriteLine($hSet, "HKCU_RunServices=1")
        FileWriteLine($hSet, "HKLM_Policies_Explorer_Run=1")
        FileWriteLine($hSet, "HKCU_Policies_Explorer_Run=1")
        FileWriteLine($hSet, "HKLM_Winlogon_Userinit=1")
        FileWriteLine($hSet, "HKLM_Winlogon_Shell=1")
        FileWriteLine($hSet, "HKCU_Windows=1")
        FileWriteLine($hSet, "HKLM_SessionManager=1")
        FileClose($hSet)
    EndIf
EndIf

; ----------------------------------------------------------------------
; Create Whitelist File if Needed
; ----------------------------------------------------------------------
If Not FileExists($g_whitelistFile) Then
    Local $hWhite = FileOpen($g_whitelistFile, $FO_OVERWRITE)
    If $hWhite <> -1 Then
        FileWriteLine($hWhite, "; Whitelisted startup/task entries for StartupMonitor64")
        FileClose($hWhite)
    EndIf
EndIf

; ----------------------------------------------------------------------
; Create Blacklist File if Needed
; ----------------------------------------------------------------------
If Not FileExists($g_blacklistFile) Then
    Local $hBlack = FileOpen($g_blacklistFile, $FO_OVERWRITE)
    If $hBlack <> -1 Then
        FileWriteLine($hBlack, "; Blacklisted startup/task entries for StartupMonitor64")
        FileWriteLine($hBlack, "; Add entries under [Startup] or [Task] sections, one per line as a key (value can be left blank)")
        FileWriteLine($hBlack, "[Startup]")
        FileWriteLine($hBlack, ";Example: badprogram.exe")
        FileWriteLine($hBlack, "[Task]")
        FileWriteLine($hBlack, ";Example: bad task name")
        FileClose($hBlack)
    EndIf
EndIf

; ----------------------------------------------------------------------
; Read Settings
; ----------------------------------------------------------------------
Global $g_showTray = IniRead($g_settingsFile, "Options", "ShowTrayIcon", "1")
Global $g_clearLog = IniRead($g_settingsFile, "Options", "ClearLogOnStart", "0")
Global $g_monitorInterval = IniRead($g_settingsFile, "Options", "MonitorTime", "3000")
Global $g_showBlacklistMsgBox = IniRead($g_settingsFile, "Options", "ShowBlacklistMsgBox", "1")
Global $g_persistentBaseline = IniRead($g_settingsFile, "Options", "PersistentBaseline", "1")
Global $g_monitorTasks = IniRead($g_settingsFile, "Options", "MonitorTasks", "1")
Global $g_reviewWindowWidth = Number(IniRead($g_settingsFile, "Options", "ReviewWindowWidth", "500"))
Global $g_reviewWindowHeight = Number(IniRead($g_settingsFile, "Options", "ReviewWindowHeight", "400"))
If Number($g_monitorInterval) < 1500 Then $g_monitorInterval = 1500
If $g_reviewWindowWidth < 300 Then $g_reviewWindowWidth = 500
If $g_reviewWindowHeight < 200 Then $g_reviewWindowHeight = 400

; Read Extra Checks
Global $g_extraChecks = [ _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKCU_RunOnce", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKLM_RunOnce", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKCU_Explorer_UserShellFolders", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKCU_Explorer_ShellFolders", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKLM_Explorer_ShellFolders", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKLM_Explorer_UserShellFolders", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKLM_RunServicesOnce", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKCU_RunServicesOnce", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKLM_RunServices", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKCU_RunServices", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKLM_Policies_Explorer_Run", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKCU_Policies_Explorer_Run", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKLM_Winlogon_Userinit", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKLM_Winlogon_Shell", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKCU_Windows", "1"), _
    IniRead($g_settingsFile, "ExtraStartupChecks", "HKLM_SessionManager", "1") _
]

; ----------------------------------------------------------------------
; Tray Icon Handling
; ----------------------------------------------------------------------
If $g_showTray = "1" Then
    TraySetState($TRAY_ICONSTATE_SHOW)
Else
    TraySetState($TRAY_ICONSTATE_HIDE)
EndIf

; ----------------------------------------------------------------------
; Clear Log if Setting Enabled
; ----------------------------------------------------------------------
If $g_clearLog = "1" Then
    If FileExists($g_logFile) Then FileDelete($g_logFile)
EndIf

; ----------------------------------------------------------------------
; Prune Old Log Entries (Older than 30 days)
; ----------------------------------------------------------------------
_PruneOldLogEntries_LineByLine()

; ----------------------------------------------------------------------
; Log program start with current tray status
; ----------------------------------------------------------------------
_LogWrite("Startup Monitor started. Tray icon is " & ($g_showTray = "1" ? "visible." : "hidden."))

; ----------------------------------------------------------------------
; Persistent Baseline Snapshot Management
; ----------------------------------------------------------------------
Global $g_initialEntries, $g_initialTasks
If $g_persistentBaseline = "1" Then
    ; -- Startup Entries Baseline --
    If FileExists($g_initialStartupFile) Then
        $g_initialEntries = _LoadSnapshotEntries($g_initialStartupFile)
    Else
        $g_initialEntries = _GetAllStartupEntries_Map(True)
        _SaveSnapshotEntries($g_initialEntries, $g_initialStartupFile)
    EndIf
    ; -- Scheduled Tasks Baseline --
    If $g_monitorTasks = "1" Then
        If FileExists($g_initialTasksFile) Then
            $g_initialTasks = _LoadSnapshotEntries($g_initialTasksFile)
        Else
            $g_initialTasks = _GetAllScheduledTasks_Map(True)
            _SaveSnapshotEntries($g_initialTasks, $g_initialTasksFile)
        EndIf
    EndIf
EndIf

; ----------------------------------------------------------------------
; Get Initial Startup Entries and Scheduled Tasks (with metadata)
; ----------------------------------------------------------------------
Global $g_oldEntries = _GetAllStartupEntries_Map(True)
Global $g_oldTasks
If $g_monitorTasks = "1" Then
    $g_oldTasks = _GetAllScheduledTasks_Map(True)
EndIf

; ----------------------------------------------------------------------
; Baseline diff detection on startup (immediate detection of changes made while not running)
; ----------------------------------------------------------------------
If $g_persistentBaseline = "1" Then
    _ReportBaselineDiff("Startup", $g_initialEntries, $g_oldEntries)
    If $g_monitorTasks = "1" Then
        _ReportBaselineDiff("Task", $g_initialTasks, $g_oldTasks)
    EndIf
EndIf

; ----------------------------------------------------------------------
; Main Monitoring Loop
; ----------------------------------------------------------------------
While 1
    Sleep(Number($g_monitorInterval))

    ; --- Startup Entries Monitoring ---
    Local $newEntries = _GetAllStartupEntries_Map(True)
    _CheckAndHandleChanges("Startup", $g_oldEntries, $newEntries)
    If $g_persistentBaseline = "1" Then
        _ReportBaselineDiff("Startup", $g_initialEntries, $newEntries)
    EndIf

    ; Update the old entries after any removals (to avoid repeated alerts)
    $g_oldEntries = $newEntries

    ; --- Scheduled Tasks Monitoring ---
    If $g_monitorTasks = "1" Then
        Local $newTasks = _GetAllScheduledTasks_Map(True)
        _CheckAndHandleChanges("Task", $g_oldTasks, $newTasks)
        If $g_persistentBaseline = "1" Then
            _ReportBaselineDiff("Task", $g_initialTasks, $newTasks)
        EndIf
        ; Update the old tasks after any removals (to avoid repeated alerts)
        $g_oldTasks = $newTasks
    EndIf
WEnd

; ------------------------------------------------------------------------------
; Persistent Baseline: Save and Load (for both startup and tasks)
; ------------------------------------------------------------------------------
Func _SaveSnapshotEntries($dict, $file)
    Local $h = FileOpen($file, $FO_OVERWRITE)
    If $h = -1 Then Return
    For $key In $dict.Keys
        Local $val = $dict.Item($key)
        FileWriteLine($h, $key & "=" & $val[0])
    Next
    FileClose($h)
EndFunc

Func _LoadSnapshotEntries($file)
    Local $dict = ObjCreate("Scripting.Dictionary")
    If Not FileExists($file) Then Return $dict
    Local $arr = FileReadToArray($file)
    If @error Then Return $dict
    For $i = 0 To UBound($arr)-1
        Local $line = $arr[$i]
        If StringLeft($line, 1) = ";" Or StringStripWS($line, 8) = "" Then ContinueLoop
        Local $parts = StringSplit($line, "=", 2)
        If UBound($parts) = 2 Then
            Local $key = $parts[0]
            Local $val = $parts[1]
            Local $meta[2]
            $meta[0] = $val
            $meta[1] = _StringHash($val)
            $dict.Add($key, $meta)
        EndIf
    Next
    Return $dict
EndFunc

; ------------------------------------------------------------------------------
; PATCHED _ReportBaselineDiff to prevent repeating alert for removed entries
; ------------------------------------------------------------------------------
Func _ReportBaselineDiff($type, $baseline, $current)
    Local $added = _MapDiff($current, $baseline)
    Local $removed = _MapDiff($baseline, $current)

    ; Track processed items to show each only once
    Local $processed = ObjCreate("Scripting.Dictionary")

    ; Handle "added" entries (present now but not in baseline)
    For $key In $added.Keys
        If $processed.Exists($key) Then ContinueLoop
        Local $meta = $current.Item($key)
        Local $target = $meta[0]
        Local $programName = _ExtractProgramName($target)
        Local $msg = "*** New " & ($type = "Startup" ? "startup entry" : "scheduled task") & " detected since original baseline! ***" & @CRLF & @CRLF & _
            "- Program Name: " & @CRLF & $programName & @CRLF & @CRLF & _
            "- " & ($type = "Startup" ? "Startup Location" : "Task File Path") & ": " & @CRLF & $key & @CRLF & @CRLF & _
            "- " & ($type = "Startup" ? "Startup Target" : "Task Action") & ": " & @CRLF & $target & @CRLF & @CRLF & _
            "- Do you wish to allow this change?"
        Local $response = MsgBox($MB_ICONWARNING + $MB_YESNO + $MB_TOPMOST, "StartupMonitor64: Baseline Difference", $msg)
        If $response = $IDNO Then
            _LogWrite("User denied baseline-detected " & ($type = "Startup" ? "entry" : "scheduled task") & ", removing: " & $key & " | Target: " & $target)
            _AddToBlackList($type, $key, $target)
            If $type = "Startup" Then
                _RemoveStartupEntry($key)
            ElseIf $type = "Task" Then
                _RemoveScheduledTask($key)
            EndIf
        ; Remove from current set to avoid repeated alerts
            If IsObj($current) And $current.Exists($key) Then $current.Remove($key)
        Else
            _LogWrite("User allowed baseline-detected " & ($type = "Startup" ? "entry" : "scheduled task") & ": " & $key & " | Target: " & $target)
            _AddToWhiteList($type, $key, $target)
            ; PATCH: Prevent repeated alerts by adding this entry to $baseline
            If IsObj($baseline) And Not $baseline.Exists($key) Then
                Local $metaArr[2]
                $metaArr[0] = $target
                $metaArr[1] = $meta[1]
                $baseline.Add($key, $metaArr)
            EndIf
        EndIf
        $processed.Add($key, True)
    Next

    ; Handle "removed" entries (present in baseline but missing now)
    ; PATCH: Prevent repeated alerts by restoring the entry to $current if No is selected
    For $key In $removed.Keys
        If $processed.Exists($key) Then ContinueLoop
        Local $meta = $baseline.Item($key)
        Local $target = $meta[0]
        Local $programName = _ExtractProgramName($target)
        Local $msg = "*** " & ($type = "Startup" ? "Startup entry" : "Scheduled task") & " REMOVED since original baseline! ***" & @CRLF & @CRLF & _
            "- Program Name: " & @CRLF & $programName & @CRLF & @CRLF & _
            "- " & ($type = "Startup" ? "Startup Location" : "Task File Path") & ": " & @CRLF & $key & @CRLF & @CRLF & _
            "- " & ($type = "Startup" ? "Startup Target" : "Task Action") & ": " & @CRLF & $target & @CRLF & @CRLF & _
            "- Do you wish to allow this removal?" & @CRLF & "(No = blacklist it from being deleted in future)"
        Local $response = MsgBox($MB_ICONWARNING + $MB_YESNO + $MB_TOPMOST, "StartupMonitor64: Baseline Removal", $msg)
        If $response = $IDNO Then
            _LogWrite("User denied REMOVAL of baseline " & ($type = "Startup" ? "entry" : "scheduled task") & ": " & $key & " | Target: " & $target)
            _AddToBlackList($type, $key, $target)
            ; PATCH: Prevent repeated alerts by restoring this entry to $current
            If IsObj($current) And Not $current.Exists($key) Then
                Local $metaArr[2]
                $metaArr[0] = $target
                $metaArr[1] = $meta[1]
                $current.Add($key, $metaArr)
            EndIf
        Else
            _LogWrite("User allowed removal of baseline " & ($type = "Startup" ? "entry" : "scheduled task") & ": " & $key & " | Target: " & $target)
            _AddToWhiteList($type, $key, $target)
        EndIf
        $processed.Add($key, True)
    Next
EndFunc

; ------------------------------------------------------------------------------
; _CheckAndHandleChanges: Adds to Blacklist/Whitelist automatically, shows msgbox for blacklist if enabled.
; Uses MsgBox for single entries, GUI review window for multiples, and passes window size from settings.
; ------------------------------------------------------------------------------
Func _CheckAndHandleChanges($type, $oldDict, ByRef $newDict)
    Local $processed = ObjCreate("Scripting.Dictionary")

    ; Detect additions
    Local $added = _MapDiff($newDict, $oldDict)
    Local $aReviewEntries[0][3], $reviewCount = 0
    For $entry In $added.Keys
        If $processed.Exists($entry) Then ContinueLoop
        Local $meta = $newDict.Item($entry)
        Local $target = $meta[0]
        Local $programName = _ExtractProgramName($target)
        ; --- Blacklist check ---
        If _IsBlackListed($type, $entry, $target) Then
            If $g_showBlacklistMsgBox = "1" Then
                Local $alertMsg = "*** BLACKLISTED " & ($type = "Startup" ? "startup entry" : "scheduled task") & " auto-removed! ***" & @CRLF & @CRLF & _
                                  "- Program Name: " & @CRLF & $programName & @CRLF & @CRLF & _
                                  "- " & ($type = "Startup" ? "Startup Location" : "Task File Path") & ": " & @CRLF & $entry & @CRLF & @CRLF & _
                                  "- " & ($type = "Startup" ? "Startup Target" : "Task Action") & ": " & @CRLF & $target
                MsgBox($MB_ICONWARNING + $MB_TOPMOST, "StartupMonitor64: Blacklist", $alertMsg)
            EndIf
            _LogWrite("BLACKLISTED " & ($type = "Startup" ? "entry" : "scheduled task") & ", auto-removing: " & $entry & " | Target: " & $target)
            If $type = "Startup" Then
                _RemoveStartupEntry($entry)
            ElseIf $type = "Task" Then
                _RemoveScheduledTask($entry)
            EndIf
            $processed.Add($entry, True)
            If IsObj($newDict) And $newDict.Exists($entry) Then $newDict.Remove($entry)
            ContinueLoop
        EndIf
        ; --- Whitelist check ---
        If _IsWhiteListed($type, $entry, $target) Then ContinueLoop
        ; --- Add to review batch instead of prompting immediately ---
        ReDim $aReviewEntries[$reviewCount + 1][3]
        $aReviewEntries[$reviewCount][0] = $programName
        $aReviewEntries[$reviewCount][1] = $entry
        $aReviewEntries[$reviewCount][2] = $target
        $reviewCount += 1
    Next

    ; Detect modifications (silent for whitelisted, prompt for others)
    Local $modified = _MapModified($newDict, $oldDict)
    Local $aReviewModified[0][3], $reviewModCount = 0
    For $entry In $modified.Keys
        If $processed.Exists($entry) Then ContinueLoop
        Local $meta = $newDict.Item($entry)
        Local $target = $meta[0]
        Local $programName = _ExtractProgramName($target)
        ; --- Blacklist check ---
        If _IsBlackListed($type, $entry, $target) Then
            If $g_showBlacklistMsgBox = "1" Then
                Local $alertMsg = "*** BLACKLISTED MODIFIED " & ($type = "Startup" ? "startup entry" : "scheduled task") & " auto-removed! ***" & @CRLF & @CRLF & _
                                  "- Program Name: " & @CRLF & $programName & @CRLF & @CRLF & _
                                  "- " & ($type = "Startup" ? "Startup Location" : "Task File Path") & ": " & @CRLF & $entry & @CRLF & @CRLF & _
                                  "- " & ($type = "Startup" ? "Startup Target" : "Task Action") & ": " & @CRLF & $target
                MsgBox($MB_ICONWARNING + $MB_TOPMOST, "StartupMonitor64: Blacklist", $alertMsg)
            EndIf
            _LogWrite("BLACKLISTED MODIFIED " & ($type = "Startup" ? "entry" : "scheduled task") & ", auto-removing: " & $entry & " | Target: " & $target)
            If $type = "Startup" Then
                _RemoveStartupEntry($entry)
            ElseIf $type = "Task" Then
                _RemoveScheduledTask($entry)
            EndIf
            $processed.Add($entry, True)
            If IsObj($newDict) And $newDict.Exists($entry) Then $newDict.Remove($entry)
            ContinueLoop
        EndIf
        ; --- Whitelist check ---
        If _IsWhiteListed($type, $entry, $target) Then ContinueLoop
        ; --- Add to review batch instead of prompting immediately ---
        ReDim $aReviewModified[$reviewModCount + 1][3]
        $aReviewModified[$reviewModCount][0] = $programName & " (MODIFIED)"
        $aReviewModified[$reviewModCount][1] = $entry
        $aReviewModified[$reviewModCount][2] = $target
        $reviewModCount += 1
    Next

    ; --- Batch review window or MsgBox for new entries ---
    If $reviewCount = 1 Then
        ; Only one item - use MsgBox
        Local $desc = $aReviewEntries[0][0]
        Local $entryKey = $aReviewEntries[0][1]
        Local $target = $aReviewEntries[0][2]
        Local $msg = "*** " & ($type = "Startup" ? "Startup entry" : "Scheduled task") & " detected ***" & @CRLF & _
                     "- Program Name: " & $desc & @CRLF & _
                     "- Location/Key: " & $entryKey & @CRLF & _
                     "- Target: " & $target & @CRLF & _
                     "- Allow this entry?"
        Local $response = MsgBox($MB_ICONWARNING + $MB_YESNO + $MB_TOPMOST, "StartupMonitor64", $msg)
        If $response = $IDYES Then
            _LogWrite("User allowed new " & ($type = "Startup" ? "entry" : "scheduled task") & ": " & $entryKey & " | Target: " & $target)
            _AddToWhiteList($type, $entryKey, $target)
        Else
            _LogWrite("User denied new " & ($type = "Startup" ? "entry" : "scheduled task") & ", removing: " & $entryKey & " | Target: " & $target)
            _AddToBlackList($type, $entryKey, $target)
            If $type = "Startup" Then
                _RemoveStartupEntry($entryKey)
            ElseIf $type = "Task" Then
                _RemoveScheduledTask($entryKey)
            EndIf
            If IsObj($newDict) And $newDict.Exists($entryKey) Then $newDict.Remove($entryKey)
        EndIf
    ElseIf $reviewCount > 1 Then
        ; Multiple items - show review window with settings size
        Local $reviewTitle = "Review new " & ($type = "Startup" ? "startup entries" : "scheduled tasks")
        Local $aResults = ShowReviewWindow($aReviewEntries, $reviewTitle, $g_reviewWindowWidth, $g_reviewWindowHeight)
        For $i = 0 To UBound($aResults) - 1
            If $aResults[$i][0] = 1 Then ; Allowed
                _LogWrite("User allowed new " & ($type = "Startup" ? "entry" : "scheduled task") & ": " & $aResults[$i][1] & " | Target: " & $aResults[$i][2])
                _AddToWhiteList($type, $aResults[$i][1], $aResults[$i][2])
            ElseIf $aResults[$i][0] = 0 Then ; Denied
                _LogWrite("User denied new " & ($type = "Startup" ? "entry" : "scheduled task") & ", removing: " & $aResults[$i][1] & " | Target: " & $aResults[$i][2])
                _AddToBlackList($type, $aResults[$i][1], $aResults[$i][2])
                If $type = "Startup" Then
                    _RemoveStartupEntry($aResults[$i][1])
                ElseIf $type = "Task" Then
                    _RemoveScheduledTask($aResults[$i][1])
                EndIf
                If IsObj($newDict) And $newDict.Exists($aResults[$i][1]) Then $newDict.Remove($aResults[$i][1])
            EndIf
        Next
    EndIf

    ; --- Batch review window or MsgBox for modified entries ---
    If $reviewModCount = 1 Then
        ; Only one modified entry - use MsgBox
        Local $desc = $aReviewModified[0][0]
        Local $entryKey = $aReviewModified[0][1]
        Local $target = $aReviewModified[0][2]
        Local $msg = "*** " & ($type = "Startup" ? "Startup entry" : "Scheduled task") & " MODIFIED ***" & @CRLF & _
                     "- Program Name: " & $desc & @CRLF & _
                     "- Location/Key: " & $entryKey & @CRLF & _
                     "- Target: " & $target & @CRLF & _
                     "- Allow this MODIFIED entry?"
        Local $response = MsgBox($MB_ICONWARNING + $MB_YESNO + $MB_TOPMOST, "StartupMonitor64", $msg)
        If $response = $IDYES Then
            _LogWrite("User allowed MODIFIED " & ($type = "Startup" ? "entry" : "scheduled task") & ": " & $entryKey & " | Target: " & $target)
            _AddToWhiteList($type, $entryKey, $target)
        Else
            _LogWrite("User denied MODIFIED " & ($type = "Startup" ? "entry" : "scheduled task") & ", removing: " & $entryKey & " | Target: " & $target)
            _AddToBlackList($type, $entryKey, $target)
            If $type = "Startup" Then
                _RemoveStartupEntry($entryKey)
            ElseIf $type = "Task" Then
                _RemoveScheduledTask($entryKey)
            EndIf
            If IsObj($newDict) And $newDict.Exists($entryKey) Then $newDict.Remove($entryKey)
        EndIf
    ElseIf $reviewModCount > 1 Then
        ; Multiple items - show review window with settings size
        Local $reviewTitle = "Review MODIFIED " & ($type = "Startup" ? "startup entries" : "scheduled tasks")
        Local $aResults = ShowReviewWindow($aReviewModified, $reviewTitle, $g_reviewWindowWidth, $g_reviewWindowHeight)
        For $i = 0 To UBound($aResults) - 1
            If $aResults[$i][0] = 1 Then ; Allowed
                _LogWrite("User allowed MODIFIED " & ($type = "Startup" ? "entry" : "scheduled task") & ": " & $aResults[$i][1] & " | Target: " & $aResults[$i][2])
                _AddToWhiteList($type, $aResults[$i][1], $aResults[$i][2])
            ElseIf $aResults[$i][0] = 0 Then ; Denied
                _LogWrite("User denied MODIFIED " & ($type = "Startup" ? "entry" : "scheduled task") & ", removing: " & $aResults[$i][1] & " | Target: " & $aResults[$i][2])
                _AddToBlackList($type, $aResults[$i][1], $aResults[$i][2])
                If $type = "Startup" Then
                    _RemoveStartupEntry($aResults[$i][1])
                ElseIf $type = "Task" Then
                    _RemoveScheduledTask($aResults[$i][1])
                EndIf
                If IsObj($newDict) And $newDict.Exists($aResults[$i][1]) Then $newDict.Remove($aResults[$i][1])
            EndIf
        Next
    EndIf
EndFunc

; ------------------------------------------------------------------------------
; _GetAllStartupEntries_Map: Collect all current startup entries from registry
; and startup folders, and from optionally enabled extra registry locations
; Returns a dictionary of [entry_path] = [value, hash]
; If $withMeta is true, stores an array: [command/value, timestamp/hash]
; ------------------------------------------------------------------------------
Func _GetAllStartupEntries_Map($withMeta=False)
    Local $dict = ObjCreate("Scripting.Dictionary")
    _GetRegEntries_Map("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $dict, $withMeta)
    _GetRegEntries_Map("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $dict, $withMeta)
    _GetFolderEntries_Map(@StartupDir, $dict, False, $withMeta)
    _GetFolderEntries_Map(@StartupCommonDir, $dict, False, $withMeta)
    ; Extra locations
    If $g_extraChecks[0] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce", $dict, $withMeta)
    If $g_extraChecks[1] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce", $dict, $withMeta)
    If $g_extraChecks[2] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders", $dict, $withMeta)
    If $g_extraChecks[3] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders", $dict, $withMeta)
    If $g_extraChecks[4] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders", $dict, $withMeta)
    If $g_extraChecks[5] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders", $dict, $withMeta)
    If $g_extraChecks[6] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce", $dict, $withMeta)
    If $g_extraChecks[7] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce", $dict, $withMeta)
    If $g_extraChecks[8] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices", $dict, $withMeta)
    If $g_extraChecks[9] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServices", $dict, $withMeta)
    If $g_extraChecks[10] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run", $dict, $withMeta)
    If $g_extraChecks[11] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run", $dict, $withMeta)
    If $g_extraChecks[12] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit", $dict, $withMeta)
    If $g_extraChecks[13] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell", $dict, $withMeta)
    If $g_extraChecks[14] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows", $dict, $withMeta)
    If $g_extraChecks[15] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager", $dict, $withMeta)
    Return $dict
EndFunc

; ------------------------------------------------------------------------------
; _GetRegEntries_Map: Adds value names & content from registry key to the map
; ------------------------------------------------------------------------------
Func _GetRegEntries_Map($key, ByRef $dict, $withMeta=False)
    Local $i = 1, $val, $v
    While 1
        $val = RegEnumVal($key, $i)
        If @error Then ExitLoop
        $v = RegRead($key, $val)
        If $withMeta Then
            Local $arr[2]
            $arr[0] = $v
            $arr[1] = _StringHash($v)
            $dict.Add($key & "\" & $val, $arr)
        Else
            $dict.Add($key & "\" & $val, $v)
        EndIf
        $i += 1
    WEnd
EndFunc

; ------------------------------------------------------------------------------
; _GetFolderEntries_Map: Adds all file names (and hash/mtime) from folder to map
; ------------------------------------------------------------------------------
Func _GetFolderEntries_Map($folder, ByRef $dict, $recursive=False, $withMeta=False)
    Local $search = FileFindFirstFile($folder & "\*")
    If $search = -1 Then Return
    While 1
        Local $file = FileFindNextFile($search)
        If @error Then ExitLoop
        If @extended = 1 Then ; Folder
            If $recursive Then _GetFolderEntries_Map($folder & "\" & $file, $dict, $recursive, $withMeta)
        Else
            Local $path = $folder & "\" & $file
            If $withMeta Then
                Local $hash = ""
                If StringRight($file, 4) = ".lnk" Then
                    $hash = _StringHash(_GetShortcutTarget($path))
                Else
                    $hash = FileGetTime($path, 0, 1) ; last write time
                EndIf
                Local $arr[2]
                $arr[0] = $path
                $arr[1] = $hash
                $dict.Add($path, $arr)
            Else
                $dict.Add($path, $path)
            EndIf
        EndIf
    WEnd
    FileClose($search)
EndFunc

; ------------------------------------------------------------------------------
; _GetAllScheduledTasks_Map: Collects all current scheduled tasks (recursively)
; Returns a dictionary of [taskfile_path] = [command, hash]
; ------------------------------------------------------------------------------
Func _GetAllScheduledTasks_Map($withMeta=False)
    Local $dict = ObjCreate("Scripting.Dictionary")
    _GetFolderEntries_Map("C:\Windows\System32\Tasks", $dict, True, $withMeta)
    If $withMeta Then
        For $k In $dict.Keys
            Local $command = _GetTaskAction($k)
            Local $arr[2]
            $arr[0] = $command
            $arr[1] = _StringHash($command)
            $dict.Item($k) = $arr
        Next
    EndIf
    Return $dict
EndFunc

; ------------------------------------------------------------------------------
; _MapDiff: Returns keys in dictA not present in dictB as a new map (dictionary)
; ------------------------------------------------------------------------------
Func _MapDiff($dictA, $dictB)
    Local $diff = ObjCreate("Scripting.Dictionary")
    For $key In $dictA.Keys
        If Not $dictB.Exists($key) Then $diff.Add($key, $dictA.Item($key))
    Next
    Return $diff
EndFunc

; ------------------------------------------------------------------------------
; _MapModified: Returns keys in dictA and dictB that have different values/hashes
; ------------------------------------------------------------------------------
Func _MapModified($dictA, $dictB)
    Local $diff = ObjCreate("Scripting.Dictionary")
    For $key In $dictA.Keys
        If $dictB.Exists($key) Then
            If $dictA.Item($key)[1] <> $dictB.Item($key)[1] Then $diff.Add($key, $dictA.Item($key))
        EndIf
    Next
    Return $diff
EndFunc

; ------------------------------------------------------------------------------
; Blacklist Management: Add and Check
; ------------------------------------------------------------------------------
Func _IsBlackListed($type, $entry, $target)
    Local $section = $type
    Local $arr = IniReadSection($g_blacklistFile, $section)
    If @error Then Return False
    For $i = 1 To $arr[0][0]
        Local $black = $arr[$i][0]
        If $black = "" Or StringLeft($black, 1) = ";" Then ContinueLoop
        If StringInStr($entry, $black, 0) Or StringInStr($target, $black, 0) Then Return True
    Next
    Return False
EndFunc

Func _AddToBlackList($type, $entry, $target)
    If Not _IsBlackListed($type, $entry, $target) Then
        IniWrite($g_blacklistFile, $type, $entry, $target)
    EndIf
EndFunc

; ------------------------------------------------------------------------------
; Whitelist Management: Add and Check
; ------------------------------------------------------------------------------
Func _AddToWhiteList($type, $entry, $target)
    If Not _IsWhiteListed($type, $entry, $target) Then
        IniWrite($g_whitelistFile, $type, $entry, $target)
    EndIf
EndFunc

Func _IsWhiteListed($type, $entry, $target)
    Local $val = IniRead($g_whitelistFile, $type, $entry, "")
    Return $val = $target
EndFunc

; ------------------------------------------------------------------------------
; _RemoveStartupEntry: Removes the specified startup entry from registry or folder
; ------------------------------------------------------------------------------
Func _RemoveStartupEntry($entry)
    If StringLeft($entry, 18) = "HKEY_LOCAL_MACHINE" Then
        Local $remain = StringTrimLeft($entry, 19)
        Local $lastSep = StringInStr($remain, "\", 0, -1)
        Local $key = "HKEY_LOCAL_MACHINE\" & StringLeft($remain, $lastSep - 1)
        Local $val = StringTrimLeft($remain, $lastSep)
        RegDelete($key, $val)
    ElseIf StringLeft($entry, 17) = "HKEY_CURRENT_USER" Then
        Local $remain = StringTrimLeft($entry, 18)
        Local $lastSep = StringInStr($remain, "\", 0, -1)
        Local $key = "HKEY_CURRENT_USER\" & StringLeft($remain, $lastSep - 1)
        Local $val = StringTrimLeft($remain, $lastSep)
        RegDelete($key, $val)
    Else
        FileDelete($entry)
    EndIf
EndFunc

; ------------------------------------------------------------------------------
; _RemoveScheduledTask: Safely removes scheduled tasks using schtasks.exe
; ------------------------------------------------------------------------------
Func _RemoveScheduledTask($entry)
    Local $taskRoot = "C:\Windows\System32\Tasks"
    If StringLeft($entry, StringLen($taskRoot)) = $taskRoot Then
        Local $rel = StringTrimLeft($entry, StringLen($taskRoot))
        If StringLeft($rel, 1) = "\" Then $rel = StringTrimLeft($rel, 1)
        $rel = StringReplace($rel, "/", "\")
        Local $cmd = 'schtasks /Delete /TN "' & $rel & '" /F'
        RunWait(@ComSpec & " /c " & $cmd, "", @SW_HIDE)
    EndIf
EndFunc

; ------------------------------------------------------------------------------
; _LogWrite: Appends a timestamped line to the log file, with '=' as separator
; ------------------------------------------------------------------------------
Func _LogWrite($sText)
    Local $hFile = FileOpen($g_logFile, $FO_APPEND)
    If $hFile = -1 Then Return
    FileWriteLine($hFile, @YEAR & "-" & @MON & "-" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC & " = " & $sText)
    FileClose($hFile)
EndFunc

; ------------------------------------------------------------------------------
; _PruneOldLogEntries_LineByLine: Keeps only log entries from the last 30 days
; ------------------------------------------------------------------------------
Func _PruneOldLogEntries_LineByLine()
    If Not FileExists($g_logFile) Then Return
    Local $nowTS = _DateToTS(@YEAR & "-" & @MON & "-" & @MDAY)
    Local $tempFile = @ScriptDir & "\_tmp_log.txt"
    Local $rfh = FileOpen($g_logFile, $FO_READ)
    If $rfh = -1 Then Return
    Local $wfh = FileOpen($tempFile, $FO_OVERWRITE)
    If $wfh = -1 Then
        FileClose($rfh)
        Return
    EndIf
    While 1
        Local $line = FileReadLine($rfh)
        If @error Then ExitLoop
        Local $dateStr = StringLeft($line, 10)
        If StringRegExp($dateStr, "^\d{4}-\d{2}-\d{2}$") Then
            Local $lineTS = _DateToTS($dateStr)
            If $nowTS - $lineTS <= 2592000 Then
                FileWriteLine($wfh, $line)
            EndIf
        Else
            FileWriteLine($wfh, $line)
        EndIf
    WEnd
    FileClose($rfh)
    FileClose($wfh)
    FileDelete($g_logFile)
    FileMove($tempFile, $g_logFile, 1)
EndFunc

; ------------------------------------------------------------------------------
; _DateToTS: Convert YYYY-MM-DD to timestamp (seconds since epoch)
; ------------------------------------------------------------------------------
Func _DateToTS($sDate)
    Local $a = StringSplit($sDate, "-")
    If $a[0] <> 3 Then Return 0
    Return _DateDiff("s", "1970/01/01 00:00:00", $a[1] & "/" & $a[2] & "/" & $a[3] & " 00:00:00")
EndFunc

; ------------------------------------------------------------------------------
; _GetStartupEntryTarget: Gets the command/target for a startup entry (reg/file)
; ------------------------------------------------------------------------------
Func _GetStartupEntryTarget($entry)
    If StringLeft($entry, 18) = "HKEY_LOCAL_MACHINE" Or StringLeft($entry, 17) = "HKEY_CURRENT_USER" Then
        Local $remain = StringInStr($entry, "\", 0, -1)
        If $remain = 0 Then Return ""
        Local $key = StringLeft($entry, $remain - 1)
        Local $val = StringTrimLeft($entry, $remain)
        Return RegRead($key, $val)
    Else
        If StringRight($entry, 4) = ".lnk" Then
            Return _GetShortcutTarget($entry)
        Else
            Return $entry
        EndIf
    EndIf
EndFunc

; ------------------------------------------------------------------------------
; _GetShortcutTarget: Get target path from a Windows shortcut (.lnk)
; ------------------------------------------------------------------------------
Func _GetShortcutTarget($lnkFile)
    Local $objShell = ObjCreate("WScript.Shell")
    If Not IsObj($objShell) Then Return ""
    Local $objShortcut = $objShell.CreateShortcut($lnkFile)
    If Not IsObj($objShortcut) Then Return ""
    Return $objShortcut.TargetPath
EndFunc

; ------------------------------------------------------------------------------
; _ExtractProgramName: Extracts program name from a command/target path
; ------------------------------------------------------------------------------
Func _ExtractProgramName($target)
    If $target = "" Then Return ""
    Local $re = StringRegExp($target, '[^\\\/]+\.exe', 1)
    If IsArray($re) Then
        Return $re[UBound($re) - 1]
    EndIf
    Local $lastSlash = StringInStr($target, "\", 0, -1)
    If $lastSlash > 0 Then
        Return StringTrimLeft($target, $lastSlash)
    EndIf
    Return $target
EndFunc

; ------------------------------------------------------------------------------
; _GetTaskAction: Extracts the "Action" (target) from a Scheduled Task XML file
; ------------------------------------------------------------------------------
Func _GetTaskAction($taskPath)
    Local $xml = FileRead($taskPath)
    If @error Or $xml = "" Then Return ""
    Local $re = StringRegExp($xml, "<Command>(.*?)</Command>", 1)
    If IsArray($re) Then
        Return $re[0]
    EndIf
    Return ""
EndFunc

; ------------------------------------------------------------------------------
; _StringHash: Returns a simple hash of a string (for change detection, not crypto)
; ------------------------------------------------------------------------------
Func _StringHash($s)
    Local $i, $h = 0
    For $i = 1 To StringLen($s)
        $h = BitXOR($h, Asc(StringMid($s, $i, 1)))
        $h = BitAND($h * 31, 0xFFFFFFFF)
    Next
    Return $h
EndFunc

GUI.au3:
 

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiListView.au3>
#include <Array.au3>
#include <WinAPI.au3>
#include <File.au3>
#include <Date.au3>
#include <StaticConstants.au3>

Func ShowReviewWindow(ByRef $aEntries, $sTitle = "Review Startup/Task Entries", $width = 500, $height = 400)
    Local $iRows = UBound($aEntries, 1)
    If $iRows < 1 Then Return SetError(1, 0, 0)
    Local $iCols = UBound($aEntries, 2)
    If $iCols < 2 Then $iCols = 2

    Local $aResults[$iRows][3 + ($iCols > 2 ? $iCols - 2 : 0)]
    ; GUI Create
    Local $hGUI = GUICreate($sTitle, $width, $height, -1, -1, $WS_CAPTION + $WS_SYSMENU, $WS_EX_TOPMOST)

    ; ListView
    Local $lvWidth = $width - 20
    Local $lvHeight = $height - 120
    Local $idListView = GUICtrlCreateListView("Allow|Entry|Location", 10, 10, $lvWidth, $lvHeight, _
        BitOR($LVS_REPORT, $LVS_SHOWSELALWAYS, $WS_HSCROLL, $WS_VSCROLL))
    _GUICtrlListView_SetExtendedListViewStyle($idListView, BitOR($LVS_EX_CHECKBOXES, $LVS_EX_FULLROWSELECT, $LVS_EX_GRIDLINES))
    _GUICtrlListView_SetColumnWidth($idListView, 0, 45)
    _GUICtrlListView_SetColumnWidth($idListView, 1, 230)
    _GUICtrlListView_SetColumnWidth($idListView, 2, 400)

    ; Add entries
    For $i = 0 To $iRows - 1
        Local $desc = $aEntries[$i][0]
        Local $key = $aEntries[$i][1]
        Local $info = $iCols > 2 ? $aEntries[$i][2] : ""
        GUICtrlCreateListViewItem("|" & $desc & "|" & $info, $idListView)
    Next

    ; Button layout
    Local $button_width = 100
    Local $button_height = 24
    Local $btn_left = 10
    Local $btn_gap = 5

    ; Bottom button vertical position
    Local $btnVertTop1 = $height - (2 * $button_height) - $btn_gap - 10
    Local $btnVertTop2 = $btnVertTop1 + $button_height + $btn_gap

    ; Info label
    Local $labelText = "CHECKED ITEMS: Allow and add to Whitelist." & @CRLF & "UNCHECKED ITEMS: Delete and add to Blacklist."
    Local $labelInfo = GUICtrlCreateLabel($labelText, 10, $btnVertTop1 - 38, $width - 40, 36)
    GUICtrlSetFont($labelInfo, 9, -1, -1, "Segoe UI")

    ; Allow All / Deny All (left)
    Local $btnAllowAll = GUICtrlCreateButton("Select All", $btn_left, $btnVertTop1, $button_width, $button_height)
    Local $btnDenyAll = GUICtrlCreateButton("Unselect All", $btn_left, $btnVertTop2, $button_width, $button_height)

    ; OK and Cancel (right)
    Local $btn_right = $width - $button_width - 10
    Local $btnOK = GUICtrlCreateButton("OK", $btn_right, $btnVertTop1, $button_width, $button_height)
    Local $btnCancel = GUICtrlCreateButton("Cancel", $btn_right, $btnVertTop2, $button_width, $button_height)

    ; Export (centered horizontally in the GUI)
    Local $exportMsgWidth = 120
    Local $exportBtnWidth = $button_width
    Local $exportBtnLeft = Int(($width - $exportBtnWidth) / 2)
    Local $exportBtnTop = $btnVertTop2
    Local $btnExport = GUICtrlCreateButton("Export List", $exportBtnLeft, $exportBtnTop, $exportBtnWidth, $button_height)

    ; Exported message label (centered above Export INI button)
    Local $exportMsgLeft = Int(($width - $exportMsgWidth) / 2)
    Local $exportMsgTop = $exportBtnTop - 22
    Local $lblExported = GUICtrlCreateLabel("", $exportMsgLeft, $exportMsgTop, $exportMsgWidth, 20, $SS_CENTER)
    GUICtrlSetFont($lblExported, 9, 400, 0, "Segoe UI")
    GUICtrlSetColor($lblExported, 0x008000)
    GUICtrlSetBkColor($lblExported, $GUI_BKCOLOR_TRANSPARENT)

    GUISetState(@SW_SHOW, $hGUI)

    ; Now that the ListView is created, check all by default
    Local $hListView = GUICtrlGetHandle($idListView)
    For $i = 0 To $iRows - 1
        _GUICtrlListView_SetItemChecked($hListView, $i, True)
    Next

    Local $msg, $cancel = False, $exportTime = 0
    While 1
        $msg = GUIGetMsg()
        Switch $msg
            Case $GUI_EVENT_CLOSE, $btnCancel
                $cancel = True
                ExitLoop
            Case $btnAllowAll
                For $i = 0 To $iRows - 1
                    _GUICtrlListView_SetItemChecked($hListView, $i, True)
                Next
            Case $btnDenyAll
                For $i = 0 To $iRows - 1
                    _GUICtrlListView_SetItemChecked($hListView, $i, False)
                Next
            Case $btnExport
                _ExportListViewToINI($hListView, $iRows, $iCols, $aEntries)
                GUICtrlSetData($lblExported, "Exported!")
                $exportTime = TimerInit()
            Case $btnOK
                ExitLoop
        EndSwitch

        ; Hide "Exported!" message after 5 seconds
        If $exportTime > 0 And TimerDiff($exportTime) > 5000 Then
            GUICtrlSetData($lblExported, "")
            $exportTime = 0
        EndIf
    WEnd

    ; Gather results
    For $i = 0 To $iRows - 1
        $aResults[$i][0] = $cancel ? -1 : _GUICtrlListView_GetItemChecked($hListView, $i)
        $aResults[$i][1] = $aEntries[$i][1]
        $aResults[$i][2] = $aEntries[$i][0]
        If $iCols > 2 Then
            For $j = 2 To $iCols - 1
                $aResults[$i][1 + $j] = $aEntries[$i][$j]
            Next
        EndIf
    Next

    GUIDelete($hGUI)
    Return $aResults
EndFunc

Func _ExportListViewToINI($hListView, $iRows, $iCols, $aEntries)
    Local $now = _NowCalc()
    Local $dateTime = StringRegExpReplace($now, "([^\d])", "")
    Local $date = StringLeft($dateTime, 8) ; YYYYMMDD
    Local $time = StringMid($dateTime, 9, 6) ; HHMMSS
    Local $timestamp = StringFormat("%s-%s", $date, $time)
    Local $filename = @ScriptDir & "\ExportedEntries_" & $timestamp & ".ini"

    Local $section = "Entries"
    If FileExists($filename) Then FileDelete($filename)

    For $i = 0 To $iRows - 1
        Local $desc = $aEntries[$i][0]
        Local $key = $aEntries[$i][1]
        Local $info = ($iCols > 2) ? $aEntries[$i][2] : ""
        Local $line = $desc & " | " & $key
        If $iCols > 2 Then
            For $j = 2 To $iCols - 1
                $line &= " | " & $aEntries[$i][$j]
            Next
        EndIf
        IniWrite($filename, $section, "Entry" & ($i + 1), $line)
    Next
EndFunc

Please let me know what needs improving. Though I expect you may want a comprehensive security tool, this is only meant to be a first line of defence, basic program for easy use, not a fully fledged, all powerful, sentinel. Personally, I am happy with it's current functionality, but am a little concerned with the massive lists it detects. But that is probably normal behaviour in windows? I don't know.

StartupMonitor.ico

Edited by sl23

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
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...