Skip to content

Instantly share code, notes, and snippets.

@manciuszz
Last active September 6, 2024 21:42
Show Gist options
  • Save manciuszz/6220269d700b68e86d7b061d06751e8b to your computer and use it in GitHub Desktop.
Save manciuszz/6220269d700b68e86d7b061d06751e8b to your computer and use it in GitHub Desktop.
Fast 'shell:startup' and Task Scheduler Auto Application Startup replacement using AutoHotkey

Auto Executor

The compiled script can be registered to be ran on Windows startup or logon events using command line interface.

AutoExecutor.exe /add-startup command would launch AutoExecutor upon windows startup event.
AutoExecutor.exe /remove-startup command would remove AutoExecutor listening to windows startup event.
AutoExecutor.exe /add-task command would launch AutoExecutor upon windows logon event.
AutoExecutor.exe /remove-task command would remove AutoExecutor listening to windows logon event.

How it works:

The script searches for .exe, .lnk, .ahk, .bat and .cmd files inside 'Apps' folder.
Once found, it checks the filename for specific rules that would modify how the applications are going to be ran.

The currently available rules are:

  • #milliseconds_FILENAME.exe - which would delay the launch of this specific FILENAME.exe by an X amount of time in milliseconds.
    • For example: "#3000_ThrottleStop.exe" would launch 'ThrottleStop.exe' after 3 seconds.
  • !FILENAME.exe - try hiding the application window of FILENAME.exe
    • For example: "!Fix-SvChostService.bat" would launch 'Fix-SvChostService.bat' silently, without any command line pop up window.

Note, that the 'Apps' folder has to be relative to the 'AutoExecutor.exe' script file.

Features:

  • Launch applications in administrator privileges
  • Auto Start applications inside 'Apps' folder based on bound rules
#NoEnv
#SingleInstance Force
#NoTrayIcon
SetBatchLines, -1
SetKeyDelay, -1, 1
SetMouseDelay, -1
SetWinDelay, -1
SetControlDelay, -1
class Utility {
RequireAdmin() {
FULL_COMMAND_LINE := DllCall("GetCommandLine", "str")
if not (A_IsAdmin or RegExMatch(FULL_COMMAND_LINE, " /restart(?!\S)")) {
try {
FilePath := ""
if A_IsCompiled
FilePath := A_ScriptFullPath
else
FilePath := Format("{} /restart \\\""{}\\\""", A_AhkPath, A_ScriptFullPath)
psScript =
(
param($param1)
Start-Process powershell -WindowStyle Hidden -Verb RunAs -ArgumentList \"-Command Start-Process '$param1'\"
)
psScript := Format("powershell -Command &{{1}} '{2}'", psScript, FilePath)
RunWait, % psScript,, Hide
}
ExitApp
}
}
SetTimer(fn, period := -1) {
SetTimer % fn, % period
}
}
class AutoExecutor {
static FolderName := "Apps"
static ExtensionPattern := "exe|lnk|ahk|bat|cmd"
__New() {
Utility.RequireAdmin()
this.Initialize()
}
Config() {
this.ExitManager := new this.ExitManager()
this.Pattern := "i).*\.(" . this.ExtensionPattern . ")$"
this.SearchPath := A_ScriptDir . "\" . this.FolderName . "\*"
}
Initialize() {
this.Config()
this.RegisterSwitches()
this.ExecuteSwitches()
this.Search(this.SearchPath)
this.Finalize()
}
Search(SearchPath) {
Loop, Files, % SearchPath
{
FileFullPath := A_LoopFileFullPath
if !RegExMatch(FileFullPath, this.Pattern)
continue
FileName := A_LoopFileName
SleepMatch := RegExMatch(FileName, "#(\d+)_", SleepMatch)
HideFlagMatch := InStr(FileName, "!") ? 0 : 1
SleepTime := SleepMatch ? SleepMatch1 : ""
if (SleepTime > 0) {
this.LaunchAppDelayed(FileFullPath, SleepTime, HideFlagMatch)
} else {
this.LaunchApp(FileFullPath, HideFlagMatch)
}
}
}
LaunchAppDelayed(FileFullPath, Delay := -1, nShowCmd := 1) {
this.ExitManager.QueueExecution()
Utility.SetTimer(ObjBindMethod(this, "_ExitManagedLaunchApp", FileFullPath, nShowCmd), -1 * Delay)
}
_ExitManagedLaunchApp(FileFullPath, nShowCmd) {
this.LaunchApp(FileFullPath, nShowCmd)
this.ExitManager.FinishExecution()
}
LaunchApp(FileFullPath, nShowCmd := 1) {
DllCall("Shell32\ShellExecute", "UInt", 0, "Str", "open", "Str", FileFullPath, "Str", "", "Str", "", "Int", nShowCmd)
}
Finalize() {
this.ExitManager.AttemptExit()
}
RegisterSwitches() {
this.switchTable := AutoExecutor.Metadata()
this.switchesMap := {}
for boundSwitch, switchObj in this.switchTable {
this.switchesMap[boundSwitch] := ObjBindMethod(this.Bootstrapper, switchObj.fn)
}
; Map "/switch" to "-switch" for accessibility
for boundSwitch, functionName in this.switchesMap {
this.switchesMap[ StrReplace(boundSwitch, "/", "-") ] := this.switchesMap[boundSwitch]
}
}
ExecuteSwitches() {
totalArgs := A_Args.Length()
if (totalArgs = 0)
return false
processedArguments := []
argPosition := 1
while (argPosition <= totalArgs) {
argument := A_Args[ argPosition ]
if (this.switchesMap.HasKey(argument)) {
args := []
argPosition++
while (argPosition <= totalArgs && !this.switchesMap.HasKey(A_Args[ argPosition ])) {
args.Push(A_Args[ argPosition ])
argPosition++
}
processedArguments.Push({ boundSwitch: argument, arguments: args })
} else {
argPosition++
}
}
if (!processedArguments.Length()) {
MsgBox % ("Error: Unsupported command line switch has been used.")
return false
}
for idx, argumentObject in processedArguments {
boundSwitch := argumentObject.boundSwitch
arguments := argumentObject.arguments
this.switchesMap[boundSwitch].Call(arguments*)
}
ExitApp
}
class Metadata {
static _ := AutoExecutor.Metadata := new AutoExecutor.Metadata()
__New() {
this.__metadata := {}
return ObjBindMethod(this, "Register")
}
Register(__customScope, metadata := "") {
if (!metadata)
return this.Retrieve()
this.__metadata[metadata.boundSwitch] := metadata
}
Retrieve() {
return this.__metadata
}
}
class Bootstrapper {
static shortcutName := "Auto Executor.lnk"
RegisterStartup() {
static @ := AutoExecutor.Metadata({ boundSwitch: "/add-startup", fn: "RegisterStartup" })
this.Clean()
FileCreateShortcut, % A_ScriptFullPath, % Format("{1}\{2}", A_Startup, this.shortcutName), % A_ScriptDir
}
UnregisterStartup() {
static @ := AutoExecutor.Metadata({ boundSwitch: "/remove-startup", fn: "UnregisterStartup" })
FileDelete, % Format("{1}\{2}", A_Startup, this.shortcutName)
}
RegisterTaskScheduler() {
static @ := AutoExecutor.Metadata({ boundSwitch: "/add-task", fn: "RegisterTaskScheduler" })
this.Clean()
psScript =
(
param($param1)
$taskTrigger = New-ScheduledTaskTrigger -AtLogon
$taskAction = New-ScheduledTaskAction -Execute "$param1"
$taskCompatibility = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -AllowStartIfOnBatteries -Compatibility \"Win8\"
Register-ScheduledTask \"Run Auto Executor At Logon\" -Action $taskAction -Trigger $taskTrigger -Settings $taskCompatibility -RunLevel Highest
)
RunWait, % Format("powershell -Command &{{1}} '{2}'", psScript, A_ScriptFullPath),, Hide
}
UnregisterTaskScheduler() {
static @ := AutoExecutor.Metadata({ boundSwitch: "/remove-task", fn: "UnregisterTaskScheduler" })
psScript =
(
Unregister-ScheduledTask -TaskName \"Run Auto Executor At Logon\" -Confirm:$false
)
RunWait, % Format("powershell -Command &{{1}}", psScript),, Hide
}
Clean() {
this.UnregisterStartup()
this.UnregisterTaskScheduler()
}
}
class ExitManager {
__New() {
this.TOTAL_EXECUTIONS := 0
this.ExitFn := ObjBindMethod(this, "AttemptExit")
}
QueueExecution() {
this.TOTAL_EXECUTIONS++
}
FinishExecution() {
this.TOTAL_EXECUTIONS--
}
AttemptExit() {
if (this.TOTAL_EXECUTIONS > 0) {
Utility.SetTimer(this.ExitFn, -1000)
return
}
this.Exit()
}
Exit() {
ExitApp
}
}
static _ := AutoExecutor := new AutoExecutor()
}
#If (WinActive("ahk_exe notepad++.exe") || WinActive("ahk_exe code.exe"))
^R::Reload
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment