; This script contains AutoHotkey (un)installation routines. ; See the AutoHotkey v2 documentation for usage. #include inc\bounce-v1.ahk /* v1 stops here */ #requires AutoHotkey v2.0 #SingleInstance Off ; Needed for elevation with *runas. #include inc\launcher-common.ahk #include inc\HashFile.ahk #include inc\CreateAppShortcut.ahk #include inc\EnableUIAccess.ahk #include inc\ShellRun.ahk if A_LineFile = A_ScriptFullPath Install_Main Install_Main() { try { Installation.Instance := inst := Installation() method := 'InstallFull' params := [] while A_Index <= A_Args.Length { switch A_Args[A_Index], 'off' { case '/install': method := 'InstallExtraVersion' inst.SourceDir := A_Args[++A_Index] case '/uninstall': method := 'Uninstall' if A_Index < A_Args.Length && SubStr(A_Args[A_Index+1],1,1) != '/' params.Push(A_Args[++A_Index]) case '/to', '/installto': inst.InstallDir := A_Args[++A_Index] case '/elevate': inst.RequireAdmin := true case '/user': inst.UserInstall := true case '/silent': inst.Silent := true default: MsgBox 'Invalid arg "' A_Args[A_Index] '"', inst.DialogTitle, "Iconx" ExitApp 1 } } inst.%method%(params*) } catch as e { inst.ErrorBox(e, "&Exit") ExitApp 1 } } class Installation { ProductName := "AutoHotkey" ProductURL := "https://autohotkey.com" Publisher := "AutoHotkey Foundation LLC" Version := A_AhkVersion AppUserModelID := 'AutoHotkey.AutoHotkey' UserInstall := !A_IsAdmin Interpreter := A_AhkPath Silent := false ScriptProgId := 'AutoHotkeyScript' SoftwareSubKey := 'Software\AutoHotkey' RootKey => this.UserInstall ? 'HKCU' : 'HKLM' SoftwareKey => this.RootKey '\' this.SoftwareSubKey ClassesKey => this.RootKey '\Software\Classes' FileTypeKey => this.ClassesKey '\' this.ScriptProgId UninstallKey => this.RootKey '\Software\Microsoft\Windows\CurrentVersion\Uninstall\AutoHotkey' StartFolder => (this.UserInstall ? A_Programs : A_ProgramsCommon) UninstallCmd => this.CmdStr('UX\ui-uninstall.ahk', ((A_IsAdmin && this.UserInstall) ? '/elevate' : '')) QUninstallCmd => this.CmdStr('UX\install.ahk', '/uninstall /silent') DialogTitle => this.ProductName " Setup" FileItems := [] ; [{Source, Dest}] RegItems := [] ; [{Key, ValueName, Value}] PreCheck := [] ; [Callback(this)] PreAction := [] ; [Callback(this)] PostAction := [] ; [Callback(this)] ResolveInstallDir() { if hadInstallDir := this.HasProp('InstallDir') DirCreate installDir := this.InstallDir else installDir := IsSet(InstallUtil) ? InstallUtil.DefaultDir : A_ScriptDir '\..' Loop Files installDir, 'D' installDir := A_LoopFileFullPath this.InstallDir := installDir SetRegView 64 installDirs := [] for rootKey in ['HKLM', 'HKCU'] installDirs.Push RegRead(rootKey '\' this.SoftwareSubKey, 'InstallDir', '') ; Override installation mode if already installed here if installDirs[1] = installDir this.UserInstall := false else if installDirs[2] = installDir this.UserInstall := true ; If this.InstallDir wasn't set upon entry to this method... else if !hadInstallDir { ; Default to the location of an existing installation matching this.UserInstall if installDirs[this.UserInstall?2:1] this.InstallDir := installDirs[this.UserInstall?2:1] ; Default to the location and mode of any other existing installation else if installDirs[this.UserInstall?1:2] { if !A_IsAdmin && this.UserInstall { ; Use the existing all-user installation only if elevation is successful try RunWait '*runas ' DllCall('GetCommandLine', 'str') catch return ; Presume user cancelled; continue as user else ExitApp } this.InstallDir := installDirs[this.UserInstall?1:2], this.UserInstall := !this.UserInstall } } } ResolveSourceDir() { if !this.HasProp('SourceDir') { if A_IsCompiled && IsSet(UnpackFiles) this.SourceDir := UnpackFiles(this.InstallDir) else this.SourceDir := A_ScriptDir '\..' } Loop Files this.SourceDir, 'D' this.SourceDir := A_LoopFileFullPath else throw ValueError("Invalid source directory",, this.SourceDir) } HashesPath => this.InstallDir '\UX\installed-files.csv' Hashes => ( this.DefineProp('Hashes', {value: hashes := this.ReadHashes()}), hashes ) Apply() { if !DirExist(this.InstallDir) DirCreate this.InstallDir SetWorkingDir this.InstallDir ; Execute pre-check actions for item in this.PreCheck item(this) ; Detect possible conflicts before taking action this.PreApplyChecks() ; Execute pre-install actions for item in this.PreAction item(this) ; Install files for item in this.FileItems { SplitPath item.Dest,, &destDir if destDir != '' DirCreate destDir try FileCopy item.Source, item.Dest, true catch this.WarnBox 'Copy failed`nsource: ' item.Source '`ndest: ' item.Dest else { ; If source files were extracted from a zip, they may have a Zone.Identifier ; stream identifying them as coming from the Internet. These must be deleted ; to prevent warnings when the user runs the installed files. try FileDelete item.Dest ":Zone.Identifier" this.AddFileHash(item.Dest, this.Version) } } ; Install registry settings for item in this.RegItems { if item.HasProp('Value') { RegWrite(item.Value, item.Value is Integer ? 'REG_DWORD' : 'REG_SZ' , item.Key, item.ValueName) } else { try RegDelete(item.Key, item.ValueName) } } ; Execute post-install actions for item in this.PostAction try item(this) catch as e this.ErrorBox(e, "&Continue") ; Write file list to disk if this.Hashes.Count this.WriteHashes } ElevationNeeded => !A_IsAdmin && (!this.UserInstall || this.HasProp('RequireAdmin')) ElevateIfNeeded() { if this.ElevationNeeded { try RunWait '*runas ' DllCall('GetCommandLine', 'str') ExitApp } } ;{ Installation entry points InstallFull() { SetRegView 64 this.ResolveInstallDir this.ResolveSourceDir doFiles := this.InstallDir != this.SourceDir ; If a newer version is already installed, integrate with it ux := doFiles && this.GetTargetUX() if ux && VerCompare(ux.Version, this.Version) > 0 { cmd := StrReplace(ux.InstallCommand, '%1', this.SourceDir,, &replaced) if !replaced cmd .= ' "' this.SourceDir '"' if this.ElevationNeeded cmd := '*runas ' cmd try exitCode := RunWait(cmd, this.InstallDir) catch as e if InStr(e.Message e.Extra, 'cancel') exitCode := 1 else throw e ExitApp exitCode } this.ElevateIfNeeded ; If a legacy version is installed, upgrade it wowKey(k) => StrReplace(k, '\Software\', '\Software\Wow6432Node\') installedVersion := RegRead(key := wowKey(this.SoftwareKey), 'Version', '') || RegRead(key := this.SoftwareKey, 'Version', '') if SubStr(installedVersion, 1, 2) = '1.' { this.SoftwareKeyV1 := key this.UninstallKeyV1 := InStr(key, 'Wow64') ? wowKey(this.UninstallKey) : this.UninstallKey this.AddPreCheck this.PrepareUpgradeV1.Bind(, installedVersion) this.AddPreAction this.UpgradeV1.Bind(, installedVersion) } if doFiles || FileExist(this.InstallDir '\UX\AutoHotkeyUX.exe') this.Interpreter := this.InstallDir '\UX\AutoHotkeyUX.exe' if doFiles { if this.GetLinkAttrib(this.InstallDir '\v2') this.AddPreAction this.DeleteLink.Bind(, 'v2') this.AddCoreFiles 'v2' ; Give UX its own AutoHotkey.exe for a few reasons: ; 1. The Start menu shortcut needs a stable path, since pinning to taskbar creates ; a copy that won't get updated (obsolete since using 'v2' and DisplaceFile now). ; 2. LauncherConfigGui may store the path under HKCU, which mightn't get updated. ; 3. It helps differentiate the launcher from other scripts in Task Manager. ; 4. It makes the UX scripts independent from the installed interpreters. srcExe := this.SourceDir '\AutoHotkey' (A_Is64bitOS ? '64' : '32') '.exe' dstExe := this.InstallDir '\UX\AutoHotkeyUX.exe' if FileExist(srcExe) || (!FileExist(dstExe) && (srcExe := A_AhkPath)) this.AddFileCopy srcExe, 'UX\AutoHotkeyUX.exe' ; Must be relative this.AddPostAction this.UpdateV2Link this.AddUXFiles this.AddMiscFiles } this.AddPostAction this.CreateWindowSpyRedirect this.AddUninstallReg this.AddSoftwareReg this.AddFileTypeReg this.Apply if FileExist(this.InstallDir '\UX\reset-assoc.ahk') RunWait this.CmdStr('UX\reset-assoc.ahk', '/check') if !this.Silent ShellRun this.Interpreter, 'UX\ui-dash.ahk', this.InstallDir } InstallExtraVersion() { SetRegView 64 this.ResolveInstallDir this.ResolveSourceDir Loop Files this.SourceDir '\AutoHotkey*.exe' { exe := GetExeInfo(A_LoopFilePath) break } else throw Error("AutoHotkey*.exe not found in source directory",, this.SourceDir) this.ElevateIfNeeded coreDir := 'v' (this.Version := exe.Version) if VerCompare(this.Version, '2.0-') < 0 { try exe := GetExeInfo(this.InstallDir '\AutoHotkeyU32.exe') catch {} else if VerCompare(this.Version, exe.Version) > 0 && !FileExist(this.InstallDir '\v' exe.Version) { this.AddPreAction this.DisplaceV1.Bind(, exe.Version) coreDir := '.' } } this.AddCoreFiles(coreDir) if FileExist(this.SourceDir '\Compiler\Ahk2Exe.exe') { compilerVersion := GetExeInfo(this.SourceDir '\Compiler\Ahk2Exe.exe').Version installedCompiler := this.Hashes.Get('Compiler\Ahk2Exe.exe', '') if !installedCompiler || VerCompare(compilerVersion, installedCompiler.Version) > 0 this.AddCompiler(this.SourceDir '\Compiler') } this.Apply } ;} ;{ Uninstallation UninstallRegistry() { SetRegView 64 delKey this.FileTypeKey delKey this.ClassesKey '\.ahk' delKey this.SoftwareKey delKey this.UninstallKey if this.RootKey = 'HKLM' { delKey 'HKCU\' this.SoftwareSubKey delKey 'HKCU\Software\Classes\' this.ScriptProgId for k in ['AutoHotkey.exe', 'Ahk2Exe.exe'] ; made by v1 installer delKey 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\' k } delKey(key) { try RegDeleteKey key catch OSError as e if e.number != 2 ; ERROR_FILE_NOT_FOUND throw } this.NotifyAssocChanged } GetHashesForVersions(versions) { versions := ',' versions ',' files := Map(), files.CaseSense := "off" for , cfiles in this.GetComponents(v => InStr(versions, ',' v ',')) for fh in cfiles files[fh.Path] := fh return files } Uninstall(versions:='') { this.ResolveInstallDir this.ElevateIfNeeded files := versions = '' ? this.Hashes.Clone() : this.GetHashesForVersions(versions) if !files.Count && versions = '' this.GetConfirmation("Installation data missing. Files will not be deleted.", 'x') ; Close scripts and help files this.PreUninstallChecks files ; Remove registry key and certificate only if being fully uninstalled if versions = '' { this.UninstallRegistry try EnableUIAccess_DeleteCertAndKey("AutoHotkey") } ; Remove files SetWorkingDir this.InstallDir modified := "" dirs := "" for path, f in files { if !FileExist(path) continue if HashFile(path) != f.Hash { modified .= "`n" path continue } if this.InstallDir '\' path = A_AhkPath { postponed := A_AhkPath this.Hashes.Delete(path) continue } FileDelete path this.Hashes.Delete(path) SplitPath path,, &dir if dir != "" dirs .= dir "`n" } if modified != "" { this.InfoBox("The following files were not deleted as they appear to have been modified:" . modified) } ; Update or remove hashes file if this.Hashes.Count this.WriteHashes else try FileDelete this.HashesPath ; Remove empty directories for dir in StrSplit(Sort(RTrim(dirs, "`n"), 'UR'), "`n") { this.DeleteLink dir '\AutoHotkey.exe' try DirDelete dir, false } if versions = '' ; Full uninstall this.DeleteLink this.InstallDir '\v2' if IsSet(postponed) { ; Try delete via cmd.exe after we exit Run(A_ComSpec ' /c "' 'ping -n 2 127.1 > NUL & ' 'del "' postponed '" & ' 'cd %TEMP% & ' 'rmdir "' postponed '\.." & ' 'rmdir "' A_WorkingDir '"' '"',, 'Hide') } ExitApp } ;} ;{ Conflict prevention PreApplyChecks() { ; Check for files that might be overwritten writeFiles := Map(), writeFiles.CaseSense := 'off' hasChm := false unknownFiles := '' modifiedFiles := '' hashes := this.Hashes if (item := hashes.Get(this.InstallDir '\UX\AutoHotkeyUX.exe', false)) ; Erroneous entry by v2.0-beta.4 hashes.Delete(item.Path), hashes[item.Path := 'UX\AutoHotkeyUX.exe'] := item ; Make it relative for item in this.FileItems { if !(attrib := FileExist(item.Dest)) continue if InStr(attrib, 'D') this.FatalError("The following file cannot be installed because a directory by this name already exists:`n" item.Dest "`n`nNo changes have been made.") SplitPath item.Dest,, &destDir, &ext, &destName if destDir = 'v2' && this.GetLinkAttrib(destDir) continue ; Symlink should be deleted, so item.Dest won't exist. if ext = 'exe' writeFiles[this.InstallDir '\' item.Dest] := true else if ext = 'chm' hasChm := true if !(curFile := hashes.Get(item.Dest, '')) unknownFiles .= item.Dest "`n" else if destDir = 'v2' && curFile.Version && curFile.Version != this.Version { this.AddPreAction this.DisplaceFile.Bind(, item.Dest , 'v' curFile.Version '\' destName '.' ext, curFile.Version) if ext = 'exe' && FileExist('v2\' destName '_UIA.exe') this.AddPreAction this.DisplaceFile.Bind(, 'v2\' destName '_UIA.exe' , 'v' curFile.Version '\' destName '_UIA.exe', '') } else if curFile.Hash != HashFile(item.Dest) modifiedFiles .= item.Dest "`n" } ; Find any scripts being executed by files that will be overwritten if writeFiles.Has(this.InstallDir '\AutoHotkeyU32.exe') ; Rough check for v1 upgrade writeFiles[this.InstallDir '\AutoHotkey.exe'] := true ours(exe) => writeFiles.Has(exe) || writeFiles.Has(StrReplace(exe, '_UIA')) scripts := this.ScriptsUsingOurFiles(ours) ; Show confirmation prompt message := "" if scripts.Length { message .= "The following scripts will be closed automatically:`n" for w in scripts message .= this.ScriptTitle(w) "`n" message .= "`n" } if unknownFiles != '' { message .= "The following files not created by setup will be overwritten:`n" . unknownFiles message .= "`n" } if modifiedFiles != '' { message .= "The following files appear to contain modifications that will be lost:`n" . modifiedFiles message .= "`n" } if message != '' this.GetConfirmation(message) this.CloseScriptsUsingOurFiles(scripts, ours) } PreUninstallChecks(files) { ours(exe) => files.Has(this.RelativePath(exe)) scripts := this.ScriptsUsingOurFiles(ours) this.CloseScriptsUsingOurFiles(scripts, ours) } CloseScriptsUsingOurFiles(scripts, ours) { ; Close scripts and help files static WM_CLOSE := 0x10 for w in WinGetList("AutoHotkey ahk_class HH Parent") try PostMessage WM_CLOSE,,, w for w in scripts try PostMessage WM_CLOSE,,, w ; Wait for windows/scripts to close WinWaitClose "AutoHotkey ahk_class HH Parent" loop { Sleep 100 ; Refresh the list in case scripts have started/stopped scripts := this.ScriptsUsingOurFiles(ours) ; Prompt again after around 3 seconds of waiting if scripts.Length && Mod(A_Index, 30) = 0 { message := "The following scripts must be closed manually before setup can continue:`n" for w in scripts message .= this.ScriptTitle(w) "`n" this.GetConfirmation(message) } } until scripts.Length = 0 } ScriptsUsingOurFiles(ours) { scripts := [], dhw := A_DetectHiddenWindows DetectHiddenWindows true for w in WinGetList('ahk_class AutoHotkey') { if w = A_ScriptHwnd continue if ours(WinGetProcessPath(w)) scripts.Push(w) } DetectHiddenWindows dhw return scripts } ScriptTitle(wnd) { try return RegExReplace(WinGetTitle(wnd), ' - AutoHotkey v.*') catch return "(unable to retrieve title - already exited?)" } ;} ;{ Components to install AddCoreFiles(destSubDir) { this.AddFiles(this.SourceDir, destSubDir , 'AutoHotkey*.exe', 'AutoHotkey.chm' ) this.AddFiles(this.SourceDir, destSubDir = '.' ? 'Compiler' : destSubDir , 'Compiler\*.bin' ; Legacy base files for compiler - even if Ahk2Exe is not installed yet. ) ; Queue creation of UIA executable files if A_IsAdmin && this.IsTrustedLocation(this.InstallDir) && VerCompare(this.Version, '1.1.19') >= 0 Loop Files this.SourceDir '\AutoHotkey*.exe' this.AddPostAction this.MakeUIA.Bind(, destSubDir '\' A_LoopFileName) } AddMiscFiles() { this.AddFiles(this.SourceDir, '.', 'license.txt') } AddCompiler(compilerSourceDir) { this.AddFiles(compilerSourceDir, 'Compiler', 'Ahk2Exe.exe') this.AddVerb('Compile', 'Compiler\Ahk2Exe.exe', '/in "%l" %*', "Compile script") this.AddVerb('Compile-Gui', 'Compiler\Ahk2Exe.exe', '/gui /in "%l" %*', "Compile script (GUI)...") this.AddPostAction this.CreateCompilerShortcut } AddUXFiles() { this.AddFiles(this.SourceDir '\UX', 'UX', '*.ahk') this.AddFiles(this.SourceDir '\UX', 'UX\inc', 'inc\*') this.AddFiles(this.SourceDir '\UX\Templates', 'UX\Templates', '*.ahk') this.AddPostAction this.CreateStartShortcut } AddSoftwareReg() { this.AddRegValues(this.SoftwareKey, [ {ValueName: 'InstallDir', Value: this.InstallDir}, {ValueName: 'InstallCommand', Value: this.CmdStr('UX\install.ahk', '/install "%1"')}, {ValueName: 'Version', Value: this.Version}, ]) } AddUninstallReg() { this.AddRegValues(this.UninstallKey, [ {ValueName: 'DisplayName', Value: this.ProductName (this.RootKey = 'HKCU' ? " (user)" : "")}, {ValueName: 'UninstallString', Value: this.UninstallCmd}, {ValueName: 'QuietUninstallString', Value: this.QUninstallCmd}, {ValueName: 'NoModify', Value: 1}, {ValueName: 'DisplayIcon', Value: this.Interpreter}, {ValueName: 'DisplayVersion', Value: this.Version}, {ValueName: 'URLInfoAbout', Value: this.ProductURL}, {ValueName: 'Publisher', Value: this.Publisher}, {ValueName: 'InstallLocation', Value: this.InstallDir}, ]) } AddFileTypeReg() { this.AddRegValues(this.ClassesKey, [ {Key: '.ahk', Value: this.ScriptProgId}, {Key: '.ahk\ShellNew', ValueName: 'Command', Value: this.CmdStr('UX\ui-newscript.ahk', '"%1"')}, {Key: '.ahk\ShellNew', ValueName: 'FileName'}, {Key: '.ahk\PersistentHandler', Value: '{5e941d80-bf96-11cd-b579-08002b30bfeb}'} ]) this.AddRegValues(this.FileTypeKey, [ {Value: "AutoHotkey Script"}, {ValueName: 'AppUserModelID', Value: this.AppUserModelID}, ; Testing produced inconsistent results, but it seems sometimes this must be here, sometimes under the verb. {Key: 'DefaultIcon', Value: this.Interpreter ",1"}, {Key: 'Shell', Value: 'Open runas UIAccess Edit'}, ; Including 'runas' in lower-case fixes the shield icon not appearing on Windows 11. {Key: 'Shell\Open', ValueName: 'FriendlyAppName', Value: 'AutoHotkey Launcher'}, ]) this.AddRunVerbs() this.AddEditVerbIfUnset() this.AddPostAction this.NotifyAssocChanged } AddRunVerbs() { aumid := {ValueName: 'AppUserModelID', Value: this.AppUserModelID} this.AddVerb('Open', 'UX\launcher.ahk', '"%1" %*', "Run script", aumid ) this.AddVerb('RunAs', 'UX\launcher.ahk', '"%1" %*', aumid, {ValueName: 'HasLUAShield', Value: ""} ) if A_IsAdmin && this.IsTrustedLocation(this.InstallDir) { this.AddVerb('UIAccess', 'UX\launcher.ahk', '/runwith UIA "%1" %*', "Run with UI access", aumid) } ; Add *Launch as a hidden verb, accessible via Run('*Launch ' file). this.AddVerb('Launch', 'UX\launcher.ahk', '/Launch "%1" %*', "Launch", aumid, {ValueName: 'ProgrammaticAccessOnly', Value: ""} ) } AddEditVerbIfUnset() { static v1_edit_cmd := 'notepad.exe %1' ; Add edit verb only if it is undefined or has its default v1 value. if RegRead(this.FileTypeKey '\Shell\Edit\Command',, v1_edit_cmd) = v1_edit_cmd this.AddVerb('Edit', 'UX\ui-editor.ahk', '"%1"', "Edit script") } ;} ;{ Utility functions RelativePath(p) => ( i := this.InstallDir '\', SubStr(p, 1, StrLen(i)) = i ? SubStr(p, StrLen(i) + 1) : p ) CmdStr(script, args:='') => RTrim(Format((InStr(script, '.ahk') ? '"{1}" ' : '') '"{2}\{3}" {4}' , this.Interpreter, this.InstallDir, script, args)) AddRegValues(key, values) { for v in values { i := {} i.Key := key (v.HasProp('Key') ? '\' v.Key : '') i.ValueName := v.HasProp('ValueName') ? v.ValueName : '' (v is Primitive) ? i.Value := v : (v.HasProp('Value')) ? i.Value := v.Value : 0 this.RegItems.Push(i) } } AddVerb(name, script, args, values*) { this.AddRegValues(this.FileTypeKey '\Shell\' name, [ {Key: 'Command', Value: this.CmdStr(script, args)}, values* ]) } AddFileCopy(sourcePath, destPath) { this.FileItems.Push {Source: sourcePath, Dest: destPath} } AddFiles(sourceDir, destSubDir, patterns*) { destSubDir := (destSubDir != '.' ? destSubDir '\' : '') for p in patterns { Loop Files sourceDir '\' p { this.AddFileCopy A_LoopFileFullPath, destSubDir . A_LoopFileName } } } AddPreCheck(f) => this.PreCheck.Push(f) AddPreAction(f) => this.PreAction.Push(f) AddPostAction(f) => this.PostAction.Push(f) ReadHashes() { ; For maintainability, don't assume the caller has set the working dir. wd := A_WorkingDir, A_WorkingDir := this.InstallDir hashes := ReadHashes(this.HashesPath, item => FileExist(item.Path)) A_WorkingDir := wd return hashes } AddFileHash(f, v) { this.Hashes[f] := {Path: f, Hash: HashFile(f), Version: v} } WriteHashes() { s := "Hash,Version,Path,Description`r`n" for ,item in this.Hashes { if !item.HasProp('Description') { try ; Cache the file description for the launcher exe := GetExeInfo(item.Path) catch item.Description := "" else { item.Description := exe.Description if InStr(item.Description, 'AutoHotkey') item.Version := exe.Version ; Ensure accuracy for the launcher } } s .= Format('{},{},"{}","{}"`r`n', item.Hash, item.Version, item.Path, item.Description) } FileOpen(this.HashesPath, 'w').Write(s) } GetComponents(versionFilter?) { callerwd := A_WorkingDir SetWorkingDir this.InstallDir versions := Map() maxes := Map() for , fh in this.Hashes { if fh.Path ~= 'i)^UX\\|^[A-Z]:|^\\\\|^(WindowSpy\.ahk|license\.txt)$' . '|^Compiler\\(?!.*\.bin$)' continue try fh.Version := GetExeInfo(fh.Path = 'AutoHotkey.chm' ? 'AutoHotkeyU32.exe' : fh.Path).Version ; Auto-fix inaccurate versions in Hashes if !files := versions.Get(fh.Version, 0) { if IsSet(versionFilter) && !versionFilter(fh.Version) continue versions[fh.Version] := files := [] v := RegExReplace(fh.Version, '^\d+\.\d+\b\K.*') if VerCompare(prevMax := maxes.Get(v, ''), fh.Version) < 0 { maxes[v] := fh.Version if prevMax != '' versions[prevMax].superseded := true } else files.superseded := true } files.InsertAt(1, fh) } SetWorkingDir callerwd return versions } NotifyAssocChanged() { DllCall("shell32\SHChangeNotify", "uint", 0x08000000 ; SHCNE_ASSOCCHANGED , "uint", 0, "ptr", 0, "ptr", 0) } GetConfirmation(message, icon:='!') { if !this.Silent && MsgBox(message, this.DialogTitle, 'Icon' icon ' OkCancel') = 'Cancel' ExitApp 1 } WarnBox(message) { if !this.Silent MsgBox message, this.DialogTitle, "Icon!" } InfoBox(message) { if !this.Silent MsgBox message, this.DialogTitle, "Iconi" } FatalError(message) { if !this.Silent MsgBox message, this.DialogTitle, 'Iconx' ExitApp 1 } ErrorBox(err, abortText?) { ; Show a standard error dialog with stack trace etc. OnMessage(0xBAAD, newthread, -2), PostMessage(0xBAAD, 0, 0, A_ScriptHwnd), SendMessage(0xBAAD, ObjPtr(err), 0, A_ScriptHwnd), OnMessage(0xBAAD, newthread, 0) newthread(ep, *) { if ep throw ObjFromPtrAddRef(ep) if ep ; This line prevents an "unreachable" warning in v2.0. return 0 ; This line prevents continuation of the function in v2.1-alpha.3+ (if user selects Continue). DetectHiddenWindows true WinExist "ahk_class #32770 ahk_pid " ProcessExist() Loop 5 ControlHide "Button" A_Index+1 ControlSetText abortText, "Button1" return 0 } } GetTargetUX() { try { ; For registered installations, InstallCommand allows for future changes. return { Version: RegRead(this.SoftwareKey, 'Version'), InstallCommand: RegRead(this.SoftwareKey, 'InstallCommand') } } try { ; Target installation not in registry, or has no InstallCommand (e.g. too old). ; Allow non-registry installations that follow protocol as commented below. ux := {} ; Version information must be provided by the file at this.HashesPath: ux.Version := this.Hashes['UX\install.ahk'].Version ; Interpreter must be located at the path calculated below: interpreter := this.InstallDir '\UX\AutoHotkeyUX.exe' if FileExist(interpreter) { ; Additional interpreters must be installable with this command line: ux.InstallCommand := Format('"{1}" "{2}\UX\install.ahk" /install "%1"' , interpreter, this.InstallDir) return ux } } ; Otherwise, UX script or appropriate interpreter not found. } ; Delete a symbolic link, or do nothing if path does not refer to a symbolic link. DeleteLink(path) { switch this.GetLinkAttrib(path) { case 'D': DirDelete path case 'F': FileDelete path } } GetLinkAttrib(path) { attrib := DllCall('GetFileAttributes', 'str', path) ; FILE_ATTRIBUTE_REPARSE_POINT = 0x400 ; FILE_ATTRIBUTE_DIRECTORY = 0x10 return (attrib != -1 && (attrib & 0x400)) ? ((attrib & 0x10) ? 'D' : 'F') : '' } UpdateV2Link() { ; Create a symlink for AutoHotkey.exe to simplify use by tools. DllCall('CreateSymbolicLink', 'str', this.InstallDir '\v2\AutoHotkey.exe' , 'str', 'AutoHotkey' (A_Is64bitOS ? '64' : '32') '.exe', 'uint', 0) } CreateWindowSpyRedirect() { ; Permit overwrite only when upgrading a legacy v1 installation, ; or if it is known to have been created by us. if !FileExist('WindowSpy.ahk') || this.HasOwnProp('SoftwareKeyV1') || this.Hashes.Get('WindowSpy.ahk', {Hash: ''}).Hash = HashFile('WindowSpy.ahk') { FileOpen('WindowSpy.ahk', 'w').Write(' ( #include UX #include inc\bounce-v1.ahk /**/ #requires AutoHotkey v2.0 try Run('"' A_MyDocuments '\AutoHotkey\WindowSpy.ahk"'), ExitApp() #include WindowSpy.ahk )') this.AddFileHash('WindowSpy.ahk', this.Version) } } CreateStartShortcut() { if this.Hashes.Has(this.StartFolder '\AutoHotkey.lnk') try FileDelete this.StartFolder '\AutoHotkey.lnk' CreateAppShortcut( lnk := this.StartFolder '\AutoHotkey Dash.lnk', { target: this.Interpreter, args: Format('"{1}\UX\ui-dash.ahk"', this.InstallDir), desc: "AutoHotkey Dash", aumid: this.AppUserModelID, uninst: this.UninstallCmd } ) this.AddFileHash lnk, this.Version CreateAppShortcut( lnk := this.StartFolder '\AutoHotkey Window Spy.lnk', { target: this.Interpreter, args: Format('"{1}\UX\WindowSpy.ahk"', this.InstallDir), desc: "AutoHotkey Window Spy", aumid: 'AutoHotkey.WindowSpy', icon: Format('{1}\UX\inc\spy.ico', this.InstallDir), iconIndex: 0, uninst: this.UninstallCmd } ) this.AddFileHash lnk, this.Version } CreateCompilerShortcut() { CreateAppShortcut( lnk := this.StartFolder '\Ahk2Exe.lnk', { target: this.InstallDir '\Compiler\Ahk2Exe.exe', desc: "Convert .ahk to .exe", aumid: 'AutoHotkey.Ahk2Exe', uninst: this.UninstallCmd } ) this.AddFileHash lnk, this.Version } MakeUIA(baseFile) { SplitPath baseFile,, &baseDir,, &baseName baseDir := baseDir = '.' ? '' : baseDir '\' newPath := baseDir baseName '_UIA.exe' static abort := false ; Let "Abort" disable MakeUIA calls, but let other PostActions complete. while !abort { FileCopy baseFile, newPath, true try { EnableUIAccess newPath break } catch as e { try FileDelete newPath if e.What != "EndUpdateResource" { abort := true this.WarnBox "An error occurred while generating files to support the `"Run with UI access`" option. An error dialog will be shown with additional information, then setup will continue." throw e } if this.Silent { if A_Index > 4 break Sleep 500 } switch MsgBox("Unable to create " baseName "_UIA.exe. Try adding an exclusion in your antivirus software. If that doesn't work, please report the issue.`n`nError: " e.Message , this.DialogTitle, "Icon! a/r/i") { case "Abort": abort := true case "Ignore": break } } } if FileExist(newPath) this.AddFileHash newPath, '' ; For uninstall } IsTrustedLocation(path) { ; http://msdn.com/library/bb756929 other := EnvGet(A_PtrSize=8 ? "ProgramFiles(x86)" : "ProgramW6432") return InStr(path, A_ProgramFiles "\") = 1 || other && InStr(path, other "\") = 1 } ;} ;{ Upgrade from v1 PrepareUpgradeV1(installedVersion) { ; This needs to be done before conflict-checking if FileExist('license.txt') this.AddFileHash('license.txt', installedVersion) } UpgradeV1(installedVersion) { try { ; Permit failure in case AutoHotkey.exe has been deleted. exe := GetExeInfo('AutoHotkey.exe') build := RegExReplace(exe.Description, '^AutoHotkey *') } ; Set default launcher settings if IsSet(build) && ConfigRead('Launcher\v1', 'Build', '!') = '!' ConfigWrite(build, 'Launcher\v1', 'Build') if ConfigRead('Launcher\v1', 'UTF8', '') = '' && InStr(RegRead('HKCR\' this.ScriptProgId '\Shell\Open\Command',, ''), '/cp65001 ') ConfigWrite(true, 'Launcher\v1', 'UTF8') if FileExist('Compiler\Ahk2Exe.exe') this.CreateCompilerShortcut ; Record these for Uninstall add 'AutoHotkey{1}.exe', '', 'A32', 'U32', 'U64', 'A32_UIA', 'U32_UIA', 'U64_UIA' add 'Compiler\{1}.bin', 'ANSI 32-bit', 'Unicode 32-bit', 'Unicode 64-bit', 'AutoHotkeySC' add '{1}', 'Compiler\Ahk2Exe.exe', 'AutoHotkey.chm', A_WinDir '\ShellNew\Template.ahk' add(fmt, patterns*) { for p in patterns if FileExist(f := Format(fmt, p)) this.AddFileHash f, installedVersion } ; Remove obsolete files for item in ['Installer.ahk', 'AutoHotkey Website.url'] try FileDelete item ; Remove the v1 shortcuts from the Start menu name := RegRead(this.SoftwareKeyV1, 'StartMenuFolder', '') if name != '' try DirDelete A_ProgramsCommon '\' name, true ; Remove the old sub-keys, which might be in the wrong reg view try RegDeleteKey this.SoftwareKeyV1 try RegDeleteKey this.UninstallKeyV1 } DisplaceFile(sourcePath, destPath, version) { SplitPath destPath,, &destDir if destDir != "" DirCreate destDir try FileMove sourcePath, destPath catch as e throw (e.Message := OSError().Message "`nSource: " sourcePath "`nDestination: " destPath, e.Extra := "", e) try this.Hashes.Delete(sourcePath) this.AddFileHash destPath, version } DisplaceV1(v) { DirCreate dir := 'v' v displace(path) { if FileExist(path) { SplitPath path, &name this.DisplaceFile path, dir '\' name, v } } for build in ['U32', 'U64', 'A32'] { displace 'AutoHotkey' build '.exe' displace 'AutoHotkey' build '_UIA.exe' } try defaultBinSize := FileGetSize(defaultBinPath := 'Compiler\AutoHotkeySC.bin') for build in ['Unicode 32-bit', 'Unicode 64-bit', 'ANSI 32-bit'] { try if FileGetSize('Compiler\' build '.bin') = defaultBinSize this.AddFileCopy this.InstallDir '\Compiler\' build '.bin', defaultBinPath displace 'Compiler\' build '.bin' } displace 'AutoHotkey.chm' try { exe := GetExeInfo('AutoHotkey.exe') if exe.Version = v && RegExMatch(exe.Description, ' (A|U)\w+ (32|64)-bit$', &m) { ; Too early to add to FileItems, so use PostAction: this.AddPostAction this.CopyDefaultExe.Bind(, 'AutoHotkey' m.1 m.2 '.exe') } } } CopyDefaultExe(from) { try FileCopy from, 'AutoHotkey.exe', true catch return ; TODO: report to user? this.AddFileHash 'AutoHotkey.exe', this.Version } ;} }