Jump to content

Recommended Posts

Posted
Quote

put an _ArrayDisplay to see the results (Common debugging practice)

; Initialize previous entries and files
Global $aPreviousEntries = _GetRegistryEntries()
_ArrayDisplay($aPreviousEntries, "$aPreviousEntries")

Great suggestion, but I'm a bit confused why you posted that. Argumentum stated earlier that "there is no "_GetRegistryEntries() " to be found" so how come you added it?

I'm also clueless where to add it!  The Global line is used at the top with the other "Globals" yes? But the other line, where does that go?

Posted
Quote

and a friendly tip
replace the real deletes with virtual ones e.g.

ConsoleWrite("RegDelete(" & $sRegistryKey & ", " & $sNewEntry & ")" & @CRLF)
; RegDelete($sRegistryKey, $sNewEntry)
...
ConsoleWrite("FileDelete(" & $sFolderPath & "\" & $sNewFile & ")" & @CRLF)
; FileDelete($sFolderPath & "\" & $sNewFile)

until you are sure it will delete the right files

Otherwise, you risk deleting everything except what you don't want.

This makes sense, but... how do I know if it's actually doing anything?

Thanks for the tips though ;)

Posted

Do the changes I suggested have    ; *** <---

#include <File.au3>
#include <MsgBoxConstants.au3>
#include <Array.au3>

Global $sRegistryKey = "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run"
Global $sFolderPath = @StartupDir ; Use the startup directory directly
Global $sLogFile = @ScriptDir & "\startup_monitor_log.txt"

; Delete existing log file if it exists
If FileExists($sLogFile) Then
    FileDelete($sLogFile)
EndIf

; Initialize previous entries and files
Global $aPreviousEntries = _GetRegistryEntries()
_ArrayDisplay($aPreviousEntries, "$aPreviousEntries") ; *** <---

Global $aPreviousFiles = _FileListToArray($sFolderPath)
_ArrayDisplay($aPreviousFiles, "$aPreviousFiles") ; *** <---

_LogChange("Starting Check for changes") ; *** <---

While True
    Sleep(5000) ; Check every 5 seconds

    ; Check for registry changes
    Local $aCurrentEntries = _GetRegistryEntries()
    If Not _ArraysEqual($aPreviousEntries, $aCurrentEntries) Then
        $aPreviousEntries = $aCurrentEntries
        Local $sNewEntry = _GetNewEntry($aPreviousEntries, $aCurrentEntries)
        _LogChange("A new startup entry has been detected: " & $sNewEntry)
        
        If MsgBox($MB_YESNO, "Startup Entry Detected", "A new startup entry has been detected: " & $sNewEntry & ". Do you want to allow it?") = $IDNO Then
            ; Remove the unauthorized entry
            ; RegDelete($sRegistryKey, $sNewEntry)
            ConsoleWrite("RegDelete(" & $sRegistryKey & ", " & $sNewEntry & ")" & @CRLF) ; *** <---
            _LogChange("Denied startup entry: " & $sNewEntry)
        EndIf
    EndIf

    ; Check for new files in the startup folder
    Local $aCurrentFiles = _FileListToArray($sFolderPath)
    If Not _ArraysEqual($aPreviousFiles, $aCurrentFiles) Then
        $aPreviousFiles = $aCurrentFiles
        Local $sNewFile = _GetNewFile($aPreviousFiles, $aCurrentFiles)
        _LogChange("A new file has been detected in the startup folder: " & $sNewFile)
        
        If MsgBox($MB_YESNO, "File Detected", "A new file has been detected in the startup folder: " & $sNewFile & ". Do you want to allow it?") = $IDNO Then
            ; Remove the unauthorized file
            ; FileDelete($sFolderPath & "\" & $sNewFile) 
            ConsoleWrite("FileDelete(" & $sFolderPath & "\" & $sNewFile & ")" & @CRLF) ; *** <---
            _LogChange("Denied file: " & $sNewFile)
        EndIf
    EndIf
WEnd

; Function to get registry entries
Func _GetRegistryEntries()
    Local $aEntries[0] ; Start with an empty array
    Local $iIndex = 0

    ; Read all values from the registry key
    While True
        Local $sValue = RegEnumVal($sRegistryKey, $iIndex)
        If @error Then ExitLoop ; Exit loop if no more values
        ReDim $aEntries[$iIndex + 1] ; Resize array to hold new entry
        $aEntries[$iIndex] = $sValue ; Store the entry
        $iIndex += 1
    WEnd
    Return $aEntries ; Return the array of entries
EndFunc

; Function to compare two arrays
Func _ArraysEqual($aArray1, $aArray2)
    If UBound($aArray1) <> UBound($aArray2) Then Return False
    For $i = 0 To UBound($aArray1) - 1
        If $aArray1[$i] <> $aArray2[$i] Then Return False
    Next
    Return True
EndFunc

; Function to get the new entry
Func _GetNewEntry($aOldEntries, $aNewEntries)
    For $i = 0 To UBound($aNewEntries) - 1
        If Not _ArraySearch($aOldEntries, $aNewEntries[$i]) Then
            Return $aNewEntries[$i]
        EndIf
    Next
    Return ""
EndFunc

; Function to get the new file
Func _GetNewFile($aOldFiles, $aNewFiles)
    For $i = 0 To UBound($aNewFiles) - 1
        If Not _ArraySearch($aOldFiles, $aNewFiles[$i]) Then
            Return $aNewFiles[$i]
        EndIf
    Next
    Return ""
EndFunc

; Function to log changes
Func _LogChange($sMessage)
    FileWrite($sLogFile, @YEAR & "-" & @MON & "-" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC & " - " & $sMessage)  ; *** <---
EndFunc

 

I know that I know nothing

Posted (edited)

No VM, but I have  sandboxie-plus, is that good enough to test this out? ie, will it work with the registry and creating manual shortcuts in the sandboxed startup folder?

@ioa747: Thanks for the help, you were too quick! I've just changed the code to adapt to use the first suggestion by Argumentum: _WinAPI_ReadDirectoryChanges($hDirectory, $iFilter, $pBuffer, but I don't know how to test for it or to get it to do what I need.

$hDirectory     A handle to the directory to be monitored. This directory must be opened with the $FILE_LIST_DIRECTORY access right.

First off, do I put the drive/path/folder there, or do I use a variable? And what does the last part mean?

Sorry for the noob questions! I won't be offended if you leave me to it! lol

New code for file monitoring:
 

removed

EDIT: Updated the code with suggestions.

Edited by sl23
Posted

Be careful, as per help file :

Quote

The _WinAPI_ReadDirectoryChanges() function works only in synchronous mode.

It means it is a blocking function.  You can use the unblocking feature but it is a tad more complicated.  For an example, search the forum, there are a number of topics about it.

Posted
6 minutes ago, sl23 said:

No VM, but I have  sandboxie-plus

If you are comfortable with it, I guess. But nothing beats a VM.
And don't forget to backup. And to backup your backup. Backups are more important than anything else ! (!)

12 minutes ago, sl23 said:

Argumentum: _WinAPI_ReadDirectoryChanges($hDirectory, $iFilter, $pBuffer, but I don't know how to test for it or to get it to do what I need.

hmm, ..the example in the ZIP should be enough but, like @Nine said, search the forum.

At this point it would be easier/faster to code what you want to have, as a birthday gift, why not.
What you want to have is not that hard to put together, but very time consuming ( but am busy with other things 🤷‍♂️ )

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

Posted (edited)

I tried VM but that's another beast I wasn't willing to tame due to no reason for learning it. Sandboxie is straightforward.

I have plenty backups! ;) I'm also good with reinstalling windows drivers where necessary. All my apps are portable and on D drive, so no installation required.

ZIP? I missed that, but can't find it. EDIT: You mean the ForkUDF's? Ok, I'll look into that too.

My birthday just gone, so, about this time next year? :lol:

That's ok, I appreciate you taking time to help out. I'm going to try and read that PDF again see if I can start making sense of the code! :lol:

 

Edited by sl23
Posted (edited)

Just got back on this. The PDF reading is slow, but I got to chapter 5 and it's fairly basic and easy to understand so far.

I've also got fed up with using DuckAI and tried Opera's ara and couple others. The limitations are seriously annoying and I can't afford or warrant a subscription to paid services.

I then started using the GitHub free service which lasts for 30 days! That has given me a working script far quicker than DuckAI did. It's also error free and is actually doing it's job. Though I don't know how to make a registry entry to check it's working on that end. But adding a shortcut to windows startup folders is working exactly how I envisaged. Is there anything missing or anything that should be added like error checking, as gitAI mentioned it might need it.

;*****************************************
; StartupMonitor64.au3 by sl23
; Created with ISN AutoIt Studio v. 1.16
; Compiled with AutoIt v3.3.16.1
;*****************************************
#AutoIt3Wrapper_icon=StartupMonitor.ico
#AutoIt3Wrapper_Res_ProductVersion=0.0.11
#AutoIt3Wrapper_Res_Description=Monitor system startup entries.
#AutoIt3Wrapper_Res_LegalCopyright=sl23

; Generated by GitHub AI:
#RequireAdmin
#include <MsgBoxConstants.au3>
#include <File.au3>
#include <TrayConstants.au3>
#include <Date.au3>

; ----------------------------------------------------------------------
; StartupMonitor64 - Efficient, low-memory startup entry monitor
; ----------------------------------------------------------------------

; ---------------------------
; Settings and File Paths
; ---------------------------
Global Const $g_settingsFile = @ScriptDir & "\Settings.ini"
Global Const $g_logFile = @ScriptDir & "\Log.ini"

; ---------------------------
; Create Settings File With Comments if Needed
; ---------------------------
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: 1 = show, 0 = hide")
        FileWriteLine($hSet, "ShowTrayIcon=1")
        FileWriteLine($hSet, "; New log file on start: 1 = New, 0 = Keep old log. 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")
        FileClose($hSet)
    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")
If Number($g_monitorInterval) < 1500 Then $g_monitorInterval = 1500

; ---------------------------
; 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)
; Only keep recent log lines to save disk and memory
; ---------------------------
_PruneOldLogEntries_LineByLine()

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

; ---------------------------
; Get Initial Startup Entries
; Uses a map/dictionary for memory efficiency and fast lookup
; ---------------------------
Global $g_oldEntries = _GetAllStartupEntries_Map()

; ---------------------------
; Main Monitoring Loop
; Checks for new startup entries at user-defined interval
; ---------------------------
While 1
    Sleep(Number($g_monitorInterval)) ; User-configurable interval (ms), min 1500ms

    ; Collect all current startup entries as a map
    Local $newEntries = _GetAllStartupEntries_Map()

    ; Find any entries that are new (not in previous scan)
    Local $added = _MapDiff($newEntries, $g_oldEntries)

    ; If new entries found, prompt user for each one
    If $added.Count > 0 Then
        For $entry In $added.Keys
            _LogWrite("Detected new startup entry: " & $entry)
            Local $response = MsgBox($MB_ICONQUESTION + $MB_YESNO + $MB_TOPMOST, "Startup Entry Detected", _
                "A new startup entry was added:" & @CRLF & $entry & @CRLF & "Allow?")
            If $response = $IDNO Then
                _LogWrite("User denied entry, removing: " & $entry)
                _RemoveStartupEntry($entry)
            Else
                _LogWrite("User allowed entry: " & $entry)
            EndIf
        Next
    EndIf

    ; Replace old entries with the latest scan for next loop iteration
    $g_oldEntries = $newEntries

    ; Release memory from local objects (optional in AutoIt, but helps clarity)
    $newEntries = 0
    $added = 0
WEnd

; ------------------------------------------------------------------------------
; _GetAllStartupEntries_Map: Collects all current startup entries from registry
; and startup folders, returns as a Scripting.Dictionary (map)
; ------------------------------------------------------------------------------
Func _GetAllStartupEntries_Map()
    Local $dict = ObjCreate("Scripting.Dictionary")
    _GetRegEntries_Map("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $dict)
    _GetRegEntries_Map("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $dict)
    _GetFolderEntries_Map(@StartupDir, $dict)
    _GetFolderEntries_Map(@StartupCommonDir, $dict)
    Return $dict
EndFunc

; ------------------------------------------------------------------------------
; _GetRegEntries_Map: Adds value names from the given registry key to the map
; ------------------------------------------------------------------------------
Func _GetRegEntries_Map($key, ByRef $dict)
    Local $i = 1, $val
    While 1
        $val = RegEnumVal($key, $i)
        If @error Then ExitLoop
        $dict.Add($key & "\" & $val, "") ; Use full path as dictionary key
        $i += 1
    WEnd
EndFunc

; ------------------------------------------------------------------------------
; _GetFolderEntries_Map: Adds all file names from the given folder to the map
; ------------------------------------------------------------------------------
Func _GetFolderEntries_Map($folder, ByRef $dict)
    Local $search = FileFindFirstFile($folder & "\*.*")
    If $search = -1 Then Return
    While 1
        Local $file = FileFindNextFile($search)
        If @error Then ExitLoop
        $dict.Add($folder & "\" & $file, "")
    WEnd
    FileClose($search)
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, "")
    Next
    Return $diff
EndFunc

; ------------------------------------------------------------------------------
; _RemoveStartupEntry: Removes the specified startup entry from registry or folder
; ------------------------------------------------------------------------------
Func _RemoveStartupEntry($entry)
    ; Remove from registry if entry is registry path, else it's a file (folder)
    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

; ------------------------------------------------------------------------------
; _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,
; processing line-by-line (very low memory usage)
; ------------------------------------------------------------------------------
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

    ; Process log one line at a time
    While 1
        Local $line = FileReadLine($rfh)
        If @error Then ExitLoop
        Local $dateStr = StringLeft($line, 10) ; "YYYY-MM-DD"
        If StringRegExp($dateStr, "^\d{4}-\d{2}-\d{2}$") Then
            Local $lineTS = _DateToTS($dateStr)
            If $nowTS - $lineTS <= 2592000 Then ; 30 days in seconds
                FileWriteLine($wfh, $line)
            EndIf
        Else
            FileWriteLine($wfh, $line) ; Keep malformed/other lines
        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

It would be really helpful if someone could check it over and see if there's anything wrong or missing, thank you so much for your help and advice.

After some more conversing with github AI, I managed to get the revised code above. It now has a settings file and logs all entries. There's only two settings in the file, the defaults are:

[Options]
ShowTrayIcon=1
ClearLogOnStart=0
MonitorTime=3000

This is looking good so far, but unless I can get some confirmation from the experts here, I am wary of using it! So Please help me to improve it's safety, as that's the priority at the moment.

Thanks :)

EDIT:
Implemented better checking of registry keys before deletion.
Added log trimming. Entries older than 30 days are deleted.

Changed Log from TXT to INI.
Added Setting for polling rate, with a safe minimum of 1500 milliseconds, anything below this will be rounded up to 1500ms.
Fixed repeated same entries not being detected.

StartupMonitor.ico

Edited by sl23
Revised code
Posted (edited)

Ok, so this code seems to be working fine. I've compiled it to it's latest version and it's detecting and deleting entries to the windows start up folders for user and all. It is also detecting and deleting registry entries that appear in the user and machine RUN sections.

Here is a test version of StartupMonitor64, source code included, should anyone feel brave enough to test it!

EDIT: I added the icon to the above post with the source code!

Edited by sl23
  • Developers
Posted (edited)

Please do not attach zip files with compiled scripts as some AV/Search companies scan this stuff and sometimes decide to declare this website unsafe!

 So just publish the source in a code tag. 🙂

Your current attachment is removed.

Edited by Jos

SciTE4AutoIt3 Full installer Download page   - Beta files       Read before posting     How to post scriptsource   Forum etiquette  Forum Rules 
 
Live for the present,
Dream of the future,
Learn from the past.
  :)

Posted

..I remember that there were more registry entries and asked google "all registry for Run runonce etc" and the AI thing ppoped up as said:
 

AI Overview
Configure a RunOnce task on Windows
The Windows Registry contains several keys that execute commands or programs at startup. The most relevant ones are Run, RunOnce, RunServices, and RunServicesOnce, found under both HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER. Run executes a command each time a user logs in, while RunOnce executes it only once, then deletes the entry. The "Services" keys are used for services that start before the user logs in. 
Here's a breakdown of the key locations:
HKEY_LOCAL_MACHINE (HKLM):

    HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run: Runs commands for all users on the system at each logon. 

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce: Runs commands for all users once, then deletes them. 
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices: Starts services before the user logs in, for all users. 
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce: Starts services once before the user logs in, then deletes them. 

HKEY_CURRENT_USER (HKCU):

    HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run: Runs commands for the specific logged-in user at each logon. 

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce: Runs commands for the specific logged-in user once, then deletes them. 
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServices: Starts services before the specific logged-in user logs in. 
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce: Starts services once for the specific logged-in user before they log in, then deletes them. 

Additional Keys:

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\BootExecute:
    . 

This key is used to specify commands that run during the very early stages of system boot, before user logon.
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows\Load:
.
This key is used to load specific programs for the currently logged-in user upon logon, according to Super User. 

Note:

    The RunOnce keys are often used for installation programs or actions that should only happen once after a system or user-specific change. 

The RunServices keys are used for services that need to start very early in the boot process. 
The presence of these keys, and the commands they execute, can significantly impact system behavior and are often targeted by malware for persistence.

and I see that you only have 2.

And yes, what a mod. said: no exe in the text forum area. But the script and the icon are very welcomed ( and am one to share an icon when I make 'em too )

Also, this is a good read ( https://www.socinvestigation.com/monitor-modified-registry-keys-possible-windows-event-id/ )
and this one too ( https://stackoverflow.com/questions/56274139/task-scheduler-run-on-event-for-a-specific-task-only )

I know that am flooding you with a bunch of info, but I don't want you to get a false sense of security, thinking that is good enough.

PS: "Just because you're paranoid doesn't mean they aren't out to get you"
paranoid.png      :D

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

Posted

Thanks argumentum. I did wonder how many there were, but never got round to asking that. I've used up 80% of my chat time on github for the month so maybe this won't get sorted for a while. but good to know about those extra entries, thank you.

Posted

I managed to get one last request from GitHub Copilot!
I added all those locations to be monitored and added them to the settings. I did this because it may be too much in some situations where there could be too many alerts. I have also to check whether the script will detect multiple new entries to startup, but this will have to wait a month as I've run out of my 'allowance.'

I'll keep looking at it to try and learn some more and read that PDF too. Using AI has been quite interesting and has helped me see the structure of things a little. When you first start out, looking at even a small script like this is daunting! So I'm happy that it turned out so well. GitHub Copilot is definitely far more useful in this context. I know it's not my own creation, but for me, it's about the end result.

Anyway, here's the new 'improved' code:
 

;*****************************************
; StartupMonitor64.au3 by sl23
; Created with ISN AutoIt Studio v. 1.16
; Compiled with AutoIt v3.3.16.1
;*****************************************
#AutoIt3Wrapper_icon=StartupMonitor.ico
#AutoIt3Wrapper_Res_FileVersion_AutoIncrement=n
#AutoIt3Wrapper_Res_ProductVersion=0.0.12
#AutoIt3Wrapper_Res_Description=Monitor system startup entries.
#AutoIt3Wrapper_Res_LegalCopyright=sl23

; Generated by GitHub AI: https://github.com/copilot/share/0a5411ba-4bc4-88e1-9941-8044400d4976
#RequireAdmin
#include <MsgBoxConstants.au3>
#include <File.au3>
#include <TrayConstants.au3>
#include <Date.au3>

; ----------------------------------------------------------------------
; Settings and File Paths
; ----------------------------------------------------------------------
Global Const $g_settingsFile = @ScriptDir & "\Settings.ini"
Global Const $g_logFile = @ScriptDir & "\Log.ini"

; ----------------------------------------------------------------------
; Create Settings File With Comments if Needed
; ----------------------------------------------------------------------
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: 1 = show, 0 = hide")
        FileWriteLine($hSet, "ShowTrayIcon=1")
        FileWriteLine($hSet, "; New log file on start: 1 = New, 0 = Keep old log. 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, "")

        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

; ----------------------------------------------------------------------
; 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")
If Number($g_monitorInterval) < 1500 Then $g_monitorInterval = 1500

; Read Extra Checks
Global $g_extraChecks[16] = [ _
    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."))

; ----------------------------------------------------------------------
; Get Initial Startup Entries
; ----------------------------------------------------------------------
Global $g_oldEntries = _GetAllStartupEntries_Map()

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

    Local $newEntries = _GetAllStartupEntries_Map()
    Local $added = _MapDiff($newEntries, $g_oldEntries)

    If $added.Count > 0 Then
        For $entry In $added.Keys
            _LogWrite("Detected new startup entry: " & $entry)
            Local $response = MsgBox($MB_ICONQUESTION + $MB_YESNO + $MB_TOPMOST, "Startup Entry Detected", _
                "A new startup entry was added:" & @CRLF & $entry & @CRLF & "Allow?")
            If $response = $IDNO Then
                _LogWrite("User denied entry, removing: " & $entry)
                _RemoveStartupEntry($entry)
            Else
                _LogWrite("User allowed entry: " & $entry)
            EndIf
        Next
    EndIf

    $g_oldEntries = $newEntries
    $newEntries = 0
    $added = 0
WEnd

; ------------------------------------------------------------------------------
; _GetAllStartupEntries_Map: Collect all current startup entries from registry
; and startup folders, and from optionally enabled extra registry locations
; ------------------------------------------------------------------------------
Func _GetAllStartupEntries_Map()
    Local $dict = ObjCreate("Scripting.Dictionary")
    _GetRegEntries_Map("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $dict)
    _GetRegEntries_Map("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $dict)
    _GetFolderEntries_Map(@StartupDir, $dict)
    _GetFolderEntries_Map(@StartupCommonDir, $dict)

    ; Check extra locations based on settings (order matches $g_extraChecks)
    If $g_extraChecks[0] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce", $dict)
    If $g_extraChecks[1] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce", $dict)
    If $g_extraChecks[2] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders", $dict)
    If $g_extraChecks[3] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders", $dict)
    If $g_extraChecks[4] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders", $dict)
    If $g_extraChecks[5] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders", $dict)
    If $g_extraChecks[6] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce", $dict)
    If $g_extraChecks[7] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce", $dict)
    If $g_extraChecks[8] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices", $dict)
    If $g_extraChecks[9] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServices", $dict)
    If $g_extraChecks[10] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run", $dict)
    If $g_extraChecks[11] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run", $dict)
    If $g_extraChecks[12] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit", $dict)
    If $g_extraChecks[13] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell", $dict)
    If $g_extraChecks[14] = "1" Then _GetRegEntries_Map("HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows", $dict)
    If $g_extraChecks[15] = "1" Then _GetRegEntries_Map("HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager", $dict)

    Return $dict
EndFunc

; ------------------------------------------------------------------------------
; _GetRegEntries_Map: Adds value names from the given registry key to the map
; ------------------------------------------------------------------------------
Func _GetRegEntries_Map($key, ByRef $dict)
    Local $i = 1, $val
    While 1
        $val = RegEnumVal($key, $i)
        If @error Then ExitLoop
        $dict.Add($key & "\" & $val, "")
        $i += 1
    WEnd
EndFunc

; ------------------------------------------------------------------------------
; _GetFolderEntries_Map: Adds all file names from the given folder to the map
; ------------------------------------------------------------------------------
Func _GetFolderEntries_Map($folder, ByRef $dict)
    Local $search = FileFindFirstFile($folder & "\*.*")
    If $search = -1 Then Return
    While 1
        Local $file = FileFindNextFile($search)
        If @error Then ExitLoop
        $dict.Add($folder & "\" & $file, "")
    WEnd
    FileClose($search)
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, "")
    Next
    Return $diff
EndFunc

; ------------------------------------------------------------------------------
; _RemoveStartupEntry: Removes the specified startup entry from registry or folder
; ------------------------------------------------------------------------------
Func _RemoveStartupEntry($entry)
    ; Remove from registry if entry is registry path, else it's a file (folder)
    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

; ------------------------------------------------------------------------------
; _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

The only issue I have is to know if it's working, ie, how to add startup entries on purpose. Perhaps a PS1 script could help with that?

Btw, this was never meant to be a security app as such, I just found it useful to stop things like edge taking over. But with the option to enable/disable specific locations to monitor, I think this is a better version. When I can use the GitAI again, I will add tasks to be monitored too, cos I have portablised DriverBooster and it keeps adding settings there and it's really annoying!!! Even though I sorta solved it through X-Launcher config.

See you in a month, or maybe sooner! lol Thanks for the tips and advice.

Posted
...
; ----------------------------------------------------------------------
; Get Initial Startup Entries
; ----------------------------------------------------------------------
Global $g_oldEntries = _GetAllStartupEntries_Map()
...

It would be good to save those entries to an organized file and load from there because, if you run this sporadically or something changes between your script running and something else, on reboot or whatnot, then you'll be able to see that there was a changed. Mostly if you're looking for sneaky things like Edge :)  

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

Posted

Just to get this right, are you saying that the program should create a second log of all existing and adding future allowed items using that as a basis for it's checks? So, for example, first run checks current state of startup, logs that to a file, adds any new entries that you allow and uses that as a basis for monitoring? But doesn't that leave it open to possible errors? Or do I have it wrong? Could you explain a little please. :)

Posted (edited)

You are comparing what you have now with what you'll have in X seconds, all in memory.
When you load your script again, it'll do the same.

If you saved the last known state, next time you load the script you will know for a fact if anything changed since you last run the script because you have it in a file with the last known state, and not just what changed while running the script ;)

That will necessitate comparing each entry and not just a count of how many. Also, as a bad actor, one could replace an entry to keep the count intact or other reasons to just edit an entry.

Spoiler

7kz6ml.jpg

Spoiler

Paranoid-meme.jpg

:D 

..unless I've got it wrong ( don't remember the code clearly )

Edited by argumentum

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

Posted (edited)

Ok, so I've been at it again and added a load of stuff! I'll try and list what's changed:

  • Added monitoring of scheduled tasks with Whitelist/Blacklists. Every alert will add to one or the other.
  • Added option to alert user if a blacklisted item is added, this alert can be disabled.
  • Added persistence between sessions.
  • Added option to disable persistence, in case you just want a simple app to check for basic startup additions.

I have tested with windows folders and the two original registry locations but not the rest listed. I also tested the tasks and DriverBooster tasks are alerted and deleted upon user request, they are also added to the blacklist while allowed items are being correctly added to whitelist. Anything in the Blacklist will be auto-deleted and alert users with a message, you can disable the message in the settings file.

I think that's about it, as well as some cosmetic changes like text, spacing, icon change to the warning icon.

Is the code sound? Anything else you think should be added? I did ask AI about recommendations and if the code was reliable and efficient. I got seevral answers and requested they be added, but not 100% sure everything has been added. It mentioned about error checking, and some other stuff, will have to check the chat to see what. 

The compiled EXE uses around 7.5MB in use and varies between 0% and 0.1% for CPU.

What do you think?
 

;*****************************************
; 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_AutoIncrement=y
#AutoIt3Wrapper_Res_Fileversion=0.0.0.31
#AutoIt3Wrapper_Res_ProductVersion=3.3.16.1
#AutoIt3Wrapper_Res_Description=StartupMonitor64
#AutoIt3Wrapper_Res_LegalCopyright=sl23

; Generated by GitHub Copilot AI:

#RequireAdmin
#SingleInstance Force
#include <MsgBoxConstants.au3>
#include <File.au3>
#include <TrayConstants.au3>
#include <Date.au3>
#include <Array.au3>

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

; ----------------------------------------------------------------------
; Create Settings File With Comments if Needed
; ----------------------------------------------------------------------
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, "[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")
If Number($g_monitorInterval) < 1500 Then $g_monitorInterval = 1500

; 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
    $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
        $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
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
        Else
            _LogWrite("User allowed baseline-detected " & ($type = "Startup" ? "entry" : "scheduled task") & ": " & $key & " | Target: " & $target)
            _AddToWhiteList($type, $key, $target)
        EndIf
        $processed.Add($key, True)
    Next

    ; Handle "removed" entries (present in baseline but missing now)
    ; You may want to prompt or just log. Here we prompt user as well:
    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)
        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: Now adds to Blacklist/Whitelist automatically and shows msgbox for blacklist if enabled
; ------------------------------------------------------------------------------
Func _CheckAndHandleChanges($type, $oldDict, $newDict)
    Local $processed = ObjCreate("Scripting.Dictionary")

    ; Detect additions
    Local $added = _MapDiff($newDict, $oldDict)
    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
        ; --- Normal prompt ---
        Local $msg = "*** A new " & ($type = "Startup" ? "startup entry" : "scheduled task") & " was added: ***" & @CRLF & @CRLF & _
            "- Program Name: " & @CRLF & $programName & @CRLF & @CRLF & _
            "- New " & ($type = "Startup" ? "Startup Location" : "Task File Path") & ": " & @CRLF & $entry & @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: " & ($type = "Startup" ? "Startup Entry" : "Scheduled Task") & " Detected", $msg)
        If $response = $IDNO Then
            _LogWrite("User denied " & ($type = "Startup" ? "entry" : "scheduled task") & ", removing: " & $entry & " | Target: " & $target)
            _AddToBlackList($type, $entry, $target)
            If $type = "Startup" Then
                _RemoveStartupEntry($entry)
            ElseIf $type = "Task" Then
                _RemoveScheduledTask($entry)
            EndIf
        Else
            _LogWrite("User allowed " & ($type = "Startup" ? "entry" : "scheduled task") & ": " & $entry & " | Target: " & $target)
            _AddToWhiteList($type, $entry, $target)
        EndIf
    Next

    ; Detect modifications (silent for whitelisted, prompt for others)
    Local $modified = _MapModified($newDict, $oldDict)
    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
        ; --- Normal prompt ---
        Local $msg = "*** An existing " & ($type = "Startup" ? "startup entry" : "scheduled task") & " was MODIFIED: ***" & @CRLF & @CRLF & _
            "- Program Name: " & @CRLF & $programName & @CRLF & @CRLF & _
            "- " & ($type = "Startup" ? "Startup Location" : "Task File Path") & ": " & @CRLF & $entry & @CRLF & @CRLF & _
            "- New " & ($type = "Startup" ? "Target" : "Action") & ": " & @CRLF & $target & @CRLF & @CRLF & _
            "- Do you wish to allow this change?" & @CRLF & "(Selecting No will attempt to remove this entry!)"
        Local $response = MsgBox($MB_ICONWARNING + $MB_YESNO + $MB_TOPMOST, "StartupMonitor64: " & ($type = "Startup" ? "Startup Entry" : "Scheduled Task") & " MODIFIED", $msg)
        If $response = $IDNO Then
            _LogWrite("User denied MODIFIED " & ($type = "Startup" ? "entry" : "scheduled task") & ", removing: " & $entry & " | Target: " & $target)
            _AddToBlackList($type, $entry, $target)
            If $type = "Startup" Then
                _RemoveStartupEntry($entry)
            ElseIf $type = "Task" Then
                _RemoveScheduledTask($entry)
            EndIf
        Else
            _LogWrite("User allowed MODIFIED " & ($type = "Startup" ? "entry" : "scheduled task") & ": " & $entry & " | Target: " & $target)
            _AddToWhiteList($type, $entry, $target)
        EndIf
    Next
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

I will likely do some tailoring to make some  light changes to filenames and such, but other than that, for now, I think this is doing a pretty good job?

The AI told me about many other locations for malware, but tbh, this was only meant to be a light application for checking basic startup locations, like the original. But as it turned out this well, I am really happy it deletes those pesky DriverBooster tasks!

PS: I know I cheated by using AI, I am no programmer. I have been reading the PDF still, and trying to gauge how this script works. I haven't honestly picked up a vast amount of knowledge, but I am starting to get the basics and learnt some simple things like loops, data types, arrays, sorta make sense, but need to reread when not so exhausted. I'm at chapter 9 which explains GUI's.

Anyway, hope this is ok, please add some feedback :)

Edited by sl23
Revised code

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...