Console() - Customize your command prompt

Post your working scripts, libraries and tools for AHK v1.1 and older
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Console() - Customize your command prompt

Post by iseahound » 01 May 2017, 21:08

Console() lets you customize your command prompt the way you like it. By default, the console will open in the current folder you are browsing.

Material Theme | Console(, "background color #282D3F", "text color #959DCB")
Image

Examples:
Launches cmd.exe | Console()
Powershell with white text on bluish-green background. | Console("powershell", "background color #259184", "text color #FFFFFF", "window width 200", "window height 10")
Administrator Powershell | Console("powershell", "admin")
Flush DNS | Console("ipconfig /FlushDNS", "pause", "exit")

Advanced Examples:
Finds your external IP address | Console("nslookup myip.opendns.com. resolver1.opendns.com", "pause", "exit")
Finds your external IP address and pings it | Console("ping -t " RegExReplace(Console("nslookup myip.opendns.com. resolver1.opendns.com", "stdout", "hide", "exit"),"s)^.*?(?<value>\b((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])(\.|\b)){4}\b)(?!\s*Name).*$","${value}"))
Save stdout to a variable | MsgBox % stdout := Console("dir /?", "stdout", "hide", "exit")

Cosmetic Parameters:
window width 200 - Changes the window width to 200 characters. (Note: that there is a minimum window width that can not be exceeded.)
window height 10 - Changes the window height to 10 lines.
background color 259184 - Changes the background color to bluish-green. Prefixing the color value with # or 0x does not matter.
text color FFFFFF - Changes the text color to white.

Functional Parameters:
activate - Activates the console.
admin - Elevates to administrator. Shows UAC prompt.
bind - Forcefully quits all child processes when current script crashes or ends.
debug - Shows the command to be run.
exit - Runs command and then exits the console.
hide - Hides the console.
max - Maximizes the console.
min - Minimizes the console.
pause - Pauses the console after execution. Useful with exit.
root - Launches from the script's current directory.
run - Bypasses using a console and runs the executable directly.
stderr - Returns the standard error.
stdout - Returns the standard output.
wait -Waits for the command to execute completely before returning. Use exit.

Return values:
pid - Returns the process ID if stdout and stderr are not parameters.
stdout - The standard out is returned when it is a parameter.
stderr - The standard error is returned when it is a parameter.
[pid,stdout,stderr] - An array at indexes 0, 1, and 2 if both stdout and stderr are input parameters.
"" - Returns an empty string if unsuccessful. For example if the user presses "No" at the UAC elevation prompt.

Code: Select all

; Return values:
;    pid                 | Process ID of the launched process if successful.
;    stdout              | Standard output if "stdout" is a parameter.
;    stderr              | Standard error if "stderr" is a parameter.
;    [pid,stdout,stderr] | An array at indexes 0, 1, and 2 if both "stdout" and "stderr" are input parameters.
;    ""                  | Error.

Console(subroutine := "", parameters*) {
   Critical On ; Ensure that sub processes are closed properly.

   ; Get path of active window.
   _hwnd := WinExist("A")
   WinGetClass _class, ahk_id %_hwnd%
   if (_class == "ExploreWClass" || _class == "CabinetWClass")
      for window in ComObjCreate("Shell.Application").Windows
         if (window.hwnd == _hwnd)
            _path := try window.Document.Folder.Self.Path

   ; Check for original registry keys in case this script crashed during a previous execution.
   RegRead _restore, % "HKEY_CURRENT_USER\Console", % "(Backup)"

   ; Parse parameters.
   for i, p in parameters {
      activate := (p = "activate") ? 1 : activate   ; Activates the console.
      admin    := (p = "admin")    ? 1 : admin      ; Elevates to administrator. Shows UAC prompt.
      bind     := (p = "bind")     ? 1 : bind       ; Forcefully quits all child processes when current script crashes or ends.
      debug    := (p = "debug")    ? 1 : debug      ; Shows the command to be run.
      exit     := (p = "exit")     ? 1 : exit       ; Runs command and then exits the console.
      hide     := (p = "hide")     ? 1 : hide       ; Hides the console.
      max      := (p ~= "i)^max")  ? 1 : max        ; Maximizes the console.
      min      := (p ~= "i)^min")  ? 1 : min        ; Minimizes the console.
      pause    := (p = "pause")    ? 1 : pause      ; Pauses the console after execution. Useful with 'exit'.
      root     := (p = "root")     ? 1 : root       ; Launches from the script's current directory.
      run      := (p = "run")      ? 1 : run        ; Bypasses using a console and runs the executable directly.
      stderr   := (p = "stderr")   ? 1 : stderr     ; Returns the standard error. (If stderr & stdout are both selected an array will be returned.)
      stdout   := (p = "stdout")   ? 1 : stdout     ; Returns the standard output. (array[1] is stdout and array[2] is stderr.)
      wait     := (p = "wait")     ? 1 : wait       ; Waits for the command to execute completely before returning. Use 'exit'.

      window_width     := (p ~= "i)window" && p ~= "i)width")     ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : window_width
      window_height    := (p ~= "i)window" && p ~= "i)height")    ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : window_height
      background_color := (p ~= "i)background" && p ~= "i)color") ? RegExReplace(p, "(?i)^.*([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})\b.*$", "0x$3$2$1") : background_color
      text_color       := (p ~= "i)text" && p ~= "i)color")       ? RegExReplace(p, "(?i)^.*([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})\b.*$", "0x$3$2$1") : text_color
   }

   ; Process parameters.
   _color := background_color || text_color
   _cosmetic := window_width || window_height || background_color || text_color
   _return := stderr || stdout
   _path := (root) ? A_ScriptDir : (_path) ? _path : A_Desktop
   _win := (hide) ? "hide" : (max) ? "max" : (min) ? "min" : ""

   ; Escape double quote character.
   static q := Chr(0x22)

   ; Construct the command to execute.
   ; Workaround: A "stdout" or "stderr" console is not allowed to be elevated as admin consoles cannot be attached to user scripts.
   if (run) {
      _cmd := (admin)                                  ? "*RunAs " subroutine     : subroutine
   } else {
      _cmd .= (admin && (A_IsAdmin || !_return))       ? "*RunAs " ComSpec " /U"  : ComSpec " /U"   ; Output unicode characters.
      _cmd .= (_color)                                 ? " /T:0a "                : ""              ; Sets the default colors to be modified later.
      _cmd .= (exit && !_return)                       ? " /C "                   : " /K "          ; Terminate or return to the prompt.
      _cmd .= (admin)                                  ? "cd /d " q _path q " "   : ""              ; Manually change path if admin.
      _cmd .= (admin && subroutine != "" && !_return)  ? " && "                   : ""
      _cmd .= (subroutine != "" && !_return)           ? subroutine               : ""
      _cmd .= (pause && !_return)                      ? " && pause"              : ""
   }

   ; Show command for debugging.
   if (debug)
      MsgBox % _cmd

   ; Alter console registry keys for cosmetic purposes.
   if (_cosmetic && !run) {
      RegRead _window_size,      % "HKEY_CURRENT_USER\Console", % "WindowSize"
      RegRead _background_color, % "HKEY_CURRENT_USER\Console", % "ColorTable00"
      RegRead _text_color,       % "HKEY_CURRENT_USER\Console", % "ColorTable10"

      _backup .= "REG_DWORD,WindowSize,"   _window_size      "|"
      _backup .= "REG_DWORD,ColorTable00," _background_color "|"
      _backup .= "REG_DWORD,ColorTable10," _text_color

      ; Store original registry keys and do not overwrite existing ones.
      if !(_restore)
         RegWrite % "REG_SZ", % "HKEY_CURRENT_USER\Console", % "(Backup)", % _backup

      window_size := 0
      window_size |= (window_width) ? (window_width) : _window_size & 0xFFFF
      window_size |= (window_height) ? (window_height << 16) : _window_size & 0xFFFF0000

      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "WindowSize",   % (window_size)      ? window_size      : _window_size
      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "ColorTable00", % (background_color) ? background_color : _background_color
      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "ColorTable10", % (text_color)       ? text_color       : _text_color
   }

   ; Ensures that 32-bit AutoHotkey opens a 32-bit console.
   ; Workaround: Redirection is disabled for elevated consoles because RunAs fails to escalate in 32-bit console on 64-bit OS.
   if (!admin && A_Is64bitOS && A_PtrSize == 4)
      DllCall("Wow64DisableWow64FsRedirection", "ptr*", _oldRedirectionValue)

   ; Execute. Errors will fail silently due to "try".
   if (wait)
      try RunWait % _cmd, % _path, % _win, _pid
      catch
         return
   else
      try Run % _cmd, % _path, % _win, _pid
      catch
         return

   ; Restore SysWow64 redirection when running as 32-bit AutoHotkey on 64-bit OS.
   if (_oldRedirectionValue)
      if !DllCall("Wow64RevertWow64FsRedirection", "ptr", _oldRedirectionValue)
         throw Exception("SysWow64 redirection failed.")

   ; Allow detection of hidden consoles.
   _dhw := A_DetectHiddenWindows
   DetectHiddenWindows On

   ; Prevent a race condition.
   if (_cosmetic && !run) OR (activate) OR (bind || (_return && exit)) OR (_return && !run) {
      WinWait ahk_pid %_pid%,, 12 ; Process Wait flashes a console occasionally.
      if (ErrorLevel)
         throw Exception("The application or console has already exited cannot be found."
         . "Remove the activate, bind, and exit parameters if this error persists.")
   }

   ; Restore original registry keys.
   if (_restore || _backup) {
      Loop Parse, % (_restore) ? _restore : _backup , % "|"
      {
         ___ := StrSplit(A_LoopField, ",")
         RegWrite % ___.1, % "HKEY_CURRENT_USER\Console", % ___.2 , % ___.3
      }
      RegDelete % "HKEY_CURRENT_USER\Console", % "(Backup)"
   }

   ; Activate the console.
   ; Known Issue: An elevated console cannot be activated by a user level script. Install AutoHotkey with UI access.
   if (activate)
      WinActivate ahk_pid %_pid%

   ; Ensures that the process executed has a fail-safe method of termination.
   ; Spawns an independent process that does two things:
   ; (1) Terminates the child process when this owner process is no longer running. (e.g. crashes or exits)
   ; (2) Exits when the child process exits.
   if (bind || (_return && exit)) {
      VarSetCapacity(_process, 2048)
      DllCall("GetModuleFileName", "int", 0, "str", _process) ; Get name of current process.
      SplitPath _process,,,, _process
      ; Memory usage: Slowly climbs and stabilizes to 8196 KB from my testing.
      ;_bind := Comspec " /q /c for /L %n in (1,0,10) do (timeout /t 1 1>NUL && (tasklist /FI " q "PID eq "
      ;      . DllCall("GetCurrentProcessId") q " 2>NUL | find /I /N " q _process q " 1>NUL || TASKKILL /PID "
      ;      . _pid " /F 2>NUL) & (tasklist /FI " q "PID eq " _pid q " 2>NUL | find /I /N " q _pid q " 1>NUL || exit))"
      _bind := "powershell -NoProfile -command " q "& {Do {if (Get-Process -id " DllCall("GetCurrentProcessId")
               . " | where {$_.Processname -eq '" _process "'}) {sleep 1} else {Get-Process -id " _pid
               . " | foreach {$_.CloseMainWindow(); Stop-Process -id $_.id}}} while (Get-Process -id " _pid ")}" q
      Run % _bind,, hide
      VarSetCapacity(_process, 0)
   }

   ; If "stdout" or "stderr" is passed, attach to the console, and run the subroutine using that console host.
   if (_return && !run) {
      DllCall("AttachConsole", "uint", _pid)                  ; Attaching a hidden console prevents flashes.
      objShell := ComObjCreate("WScript.Shell")               ; Will allocate a new console if needed causing flashes.
      objExec := objShell.Exec(ComSpec " /C " q subroutine q) ; Windows 7: Don't call subroutine directly.
      while (!objExec.Status)
         Sleep 10
      _stdout := objExec.StdOut.ReadAll()
      _stderr := objExec.StdErr.ReadAll()
      DllCall("FreeConsole")
      Process Close, % _pid                                   ; Better than PostMessage and WinClose.
   }

   ; Restore global script settings.
   DetectHiddenWindows %_dhw%
   Critical Off

   ; Returns an array only if both stdout and stderr are wanted. Otherwise returns stdout, stderr, or the process ID.
   return (stdout && stderr) ? {0:_pid, 1:_stdout, 2:stderr} : (stdout) ? _stdout : (stderr) ? _stderr : _pid
}
Last edited by iseahound on 08 May 2020, 22:05, edited 19 times in total.

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: CommandRoot - Another Open Command Window Here

Post by iseahound » 27 Feb 2018, 02:25

2018-03-01 Update.
Old Version of CommandRoot()

This function is now extremely over-engineered. There's a lot of built in safety checks and it will still run fast.

EDIT: Added ability to change the color of the command prompt!
Last edited by iseahound on 08 May 2020, 21:16, edited 3 times in total.

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: CommandRoot - Open Command Window in Current Folder

Post by iseahound » 04 May 2020, 10:59

2020-05-04 fixes

Code: Select all

CommandRoot(subroutine := "", parameters*) {
   ; Get path of active window.
   _hwnd := WinExist("A")
   WinGetClass wClass, ahk_id %_hwnd%
   if (wClass == "ExploreWClass" || wClass == "CabinetWClass")
      for window in ComObjCreate("Shell.Application").Windows
         if (window.hwnd == _hwnd)
            _path := try window.Document.Folder.Self.Path

   ; Parse parameters.
   admin := bind := color := debug := exit := hide := max := min := pause := run := stderr := stdout := wait := 0
   for i,param in parameters {
      admin   := (param = "admin")   ? 1 : admin
      bind    := (param = "bind")    ? 1 : bind
      color   := (param ~= "color")  ? i : color
      debug   := (param = "debug")   ? 1 : debug
      exit    := (param = "exit")    ? 1 : exit
      hide    := (param = "hide")    ? 1 : hide
      max     := (param = "max")     ? 1 : max
      min     := (param = "min")     ? 1 : min
      pause   := (param = "pause")   ? 1 : pause
      root    := (param = "root")    ? 1 : root
      run     := (param = "run")     ? 1 : run
      stderr  := (param = "stderr")  ? 1 : stderr
      stdout  := (param = "stdout")  ? 1 : stdout
      wait    := (param = "wait")    ? 1 : wait
   }
   _return := (stderr || stdout) ? 1 : 0
   _path := (root) ? A_ScriptDir : (_path) ? _path : A_Desktop
   _win := (hide) ? "hide" : (max) ? "max" : (min) ? "min" : ""

   ; Escape double quote character.
   static q := Chr(0x22)

   ; Construct the command to execute.
   if (run) {
      _cmd := (admin)                                 ? "*RunAs " subroutine                      : subroutine
   } else {
      _cmd .= (admin)                                 ? "*RunAs " ComSpec " /U"                   : ComSpec " /U"
      _cmd .= (color)                                 ? " /T:" SubStr(parameters[color], -1) " "  : ""
      _cmd .= (exit && !_return)                      ? " /C "                                    : " /K "
      _cmd .= (admin)                                 ? "cd /d " q _path q " "                    : ""
      _cmd .= (admin && subroutine != "" && !_return) ? " && "                                    : ""
      _cmd .= (subroutine != "" && !_return)          ? subroutine                                : ""
      _cmd .= (pause && !_return)                     ? " && pause"                               : ""
   }

   if (debug)
      MsgBox % _cmd

   ; Disable SysWow64 redirection when running as 32-bit autohotkey on 64-bit OS. RunAs will FAIL if run as 32-bit on 64-bit OS.
   if (!admin && A_Is64bitOS && A_PtrSize == 4)
      DllCall("Wow64DisableWow64FsRedirection", "Ptr*", oldRedirectionValue)

   ; Get name of current process.
   VarSetCapacity(_process, 2048)
   DllCall("GetModuleFileName", "int", 0, "str", _process)
   SplitPath, _process,,,, _process

   ; Execute. Errors will fail silently due to "try".
   if (wait)
      try RunWait % _cmd, % _path, % _win, _pid
   else
      try Run % _cmd, % _path, % _win, _pid

   if (bind || (_return && exit)) {
      ; The following command launches a cmd.exe process that checks to see if the parent process (this script) has exited or not.
      ; If the parent process (ex. AutoHotkey.exe) that calls the child process (ex. cmd.exe) exits, then forcefully quit
      ; the child process. If the child process exits, then this process will exit.
      ; This is necessary because passing "stderr" or "stdout" will prevent the CommandRoot() process from exiting itself.
      ; Memory usage: Slowly climbs and stabilizes to 8196 KB from my testing.
      ;_exit := Comspec " /q /c for /L %n in (1,0,10) do (timeout /t 1 1>NUL && (tasklist /FI " q "PID eq "
      ;      . DllCall("GetCurrentProcessId") q " 2>NUL | find /I /N " q _process q " 1>NUL || TASKKILL /PID "
      ;      . _pid " /F 2>NUL) & (tasklist /FI " q "PID eq " _pid q " 2>NUL | find /I /N " q _pid q " 1>NUL || exit))"
      _exit := "powershell -NoProfile -command " q "& {Do {if (Get-Process -id " DllCall("GetCurrentProcessId")
               . " | where {$_.Processname -eq '" _process "'}) {sleep 1} else {Get-Process -id " _pid
               . " | foreach {$_.CloseMainWindow(); Stop-Process -id $_.id}}} while (Get-Process -id " _pid ")}" q
      Run, % _exit,, Hide
   }

   VarSetCapacity(_process, 0)

   ; Restore SysWow64.
   if (oldRedirectionValue)
      t := DllCall("Wow64RevertWow64FsRedirection", "Ptr", oldRedirectionValue) ; consider exiting if, for whatever unlikely reason, this returns False

   ; If "stdout" or "stderr" is passed, attach to the console and run subroutine.
   if (_return && !run) {
      _dhw := A_DetectHiddenWindows
      DetectHiddenWindows On
      WinWait, ahk_pid %_pid%
      DllCall("AttachConsole", "uint", _pid)
      objShell := ComObjCreate("WScript.Shell")
      objExec := objShell.Exec(subroutine)
      while (!objExec.Status)
         Sleep 10
      _stdout := objExec.StdOut.ReadAll()
      _stderr := objExec.StdErr.ReadAll()
      DllCall("FreeConsole")
      PostMessage, 0x112, 0xF060,,, ahk_pid %_pid%  ; 0x112 = WM_SYSCOMMAND, 0xF060 = SC_CLOSE
      DetectHiddenWindows %_dhw%
   }

   return (stdout && stderr) ? {0:_pid, 1:_stdout, 2:stderr} : (stdout) ? _stdout : (stderr) ? _stderr : _pid
}

burque505
Posts: 1736
Joined: 22 Jan 2017, 19:37

Re: CommandRoot - Open Command Window in Current Folder

Post by burque505 » 04 May 2020, 12:56

Hi @iseahound, I don't know how I missed this great script before. I tried the first version and the latest version on Win7, working great, except with the latest version I frequently get this error:
Spoiler
If I change line 94 back to

Code: Select all

objExec := objShell.Exec(ComSpec " /c " q subroutine q)
I never get the error. Can you see any harm in me leaving it as it is with my change?

I really like the fact that I can just use this without having to add an extra reference to stdouttovar.ahk (in one of its numerous incarnations).

Regards,
burque505

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: CommandRoot - Open Command Window in Current Folder

Post by iseahound » 04 May 2020, 18:50

It looks like you've found a bug! I wrote this script back when I was still on Windows 7, and somewhere along the way I must've changed it because I couldn't figure out the difference between the two methods. Thanks a lot, I'll revert that change to keep it Windows 7 compatible.

Also, you've stumbled upon this thread while I was working on it. I've got lots of new functionality to add and bugfixes on top of that. The "2020-05-04" version is just a copy and paste of what was currently in my folder. A copy to reference before I make changes.

burque505
Posts: 1736
Joined: 22 Jan 2017, 19:37

Re: CommandRoot - Open Command Window in Current Folder

Post by burque505 » 05 May 2020, 07:49

Looking forward to the next version then, thank you, @iseahound!
Regards,
burque505

hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: CommandRoot - Open Command Window in Current Folder

Post by hasantr » 05 May 2020, 10:28

@iseahound this is great, thanks a lot. I can get the text with stdout, but can I do this all the time. For example, the file copy operation shows "windows 10 cmd" as a percentage. I want to be able to see the progress. So I want to update stdout. Can it be done?

burque505
Posts: 1736
Joined: 22 Jan 2017, 19:37

Re: CommandRoot - Open Command Window in Current Folder

Post by burque505 » 05 May 2020, 11:10

@hasantr, you might want to also have a look at @TheArkive's script CLI - Swiss Army Knife . His example script has a couple where the progress is constantly updated.

Regards,
burque505

hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: CommandRoot - Open Command Window in Current Folder

Post by hasantr » 05 May 2020, 13:08

burque505 wrote:
05 May 2020, 11:10
@hasantr, you might want to also have a look at @TheArkive's script CLI - Swiss Army Knife . His example script has a couple where the progress is constantly updated.

Regards,
burque505
This is awesome. Thank you.

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: CommandRoot - Open Command Window in Current Folder

Post by iseahound » 06 May 2020, 12:11

2020-05-06 New Features and bugfixes
  • Renamed from CommandRoot() to Console()
  • NEW: Change background and text color using RGB values. Example: Console("powershell", "background color #259184", "text color #FFFFFF")
  • NEW: Change width and height of console. Example: Console("powershell", "background color #259184", "text color #FFFFFF", "window width 200", "window height 10")
  • FEATURE: Automatically detect and restore corrupted registry keys if execution fails on next run.
  • FEATURE: The activate parameter will activate the window.
  • FEATURE: The debug parameter shows the command before it is executed.
  • BUGFIX: A user level script can no longer launch an administrator console with stderr or stdout enabled.
  • BUGFIX: Launching multiple consoles using stderr or stdout now properly closes the consoles when finished.
  • BUGFIX: Process is now Critical to ensure that it cannot be interrupted by repeated hotkey presses.
  • BUGFIX: Removed race conditions involved with bind.
  • BUGFIX: When launching a administrator console and the user presses no at the UAC prompt, the script now exits properly and returns an empty string.
  • BUGFIX: Windows 7 compatibility has been restored with stderr and stdout.
  • REMOVED: Old color syntax like color 0a
2020-05-06 Hotfix a bug where in case of corruption the restoration of original registry keys would fail.

Code: Select all

; Return values:
;    pid                 | Process ID of the launched process if successful.
;    stdout              | Standard output if "stdout" is a parameter.
;    stderr              | Standard error if "stderr" is a parameter.
;    [pid,stdout,stderr] | An array at indexes 0, 1, and 2 if both "stdout" and "stderr" are input parameters.
;    ""                  | Error.

Console(subroutine := "", parameters*) {
   Critical On ; Ensure that sub processes are closed properly.

   ; Get path of active window.
   _hwnd := WinExist("A")
   WinGetClass _class, ahk_id %_hwnd%
   if (_class == "ExploreWClass" || _class == "CabinetWClass")
      for window in ComObjCreate("Shell.Application").Windows
         if (window.hwnd == _hwnd)
            _path := try window.Document.Folder.Self.Path

   ; Check for original registry keys in case this script crashed during a previous execution.
   RegRead _restore, % "HKEY_CURRENT_USER\Console", % "(Backup)"

   ; Parse parameters.
   for i, p in parameters {
      activate := (p = "activate") ? 1 : activate   ; Activates the console.
      admin    := (p = "admin")    ? 1 : admin      ; Elevates to administrator. Shows UAC prompt.
      bind     := (p = "bind")     ? 1 : bind       ; Forcefully quits all child processes when current script crashes or ends.
      debug    := (p = "debug")    ? 1 : debug      ; Shows the command to be run.
      exit     := (p = "exit")     ? 1 : exit       ; Runs command and then exits the console.
      hide     := (p = "hide")     ? 1 : hide       ; Hides the console.
      max      := (p ~= "i)^max")  ? 1 : max        ; Maximizes the console.
      min      := (p ~= "i)^min")  ? 1 : min        ; Minimizes the console.
      pause    := (p = "pause")    ? 1 : pause      ; Pauses the console after execution. Useful with 'exit'.
      root     := (p = "root")     ? 1 : root       ; Launches from the script's current directory.
      run      := (p = "run")      ? 1 : run        ; Bypasses using a console and runs the executable directly.
      stderr   := (p = "stderr")   ? 1 : stderr     ; Returns the standard error. (If stderr & stdout are both selected an array will be returned.)
      stdout   := (p = "stdout")   ? 1 : stdout     ; Returns the standard output. (array[1] is stdout and array[2] is stderr.)
      wait     := (p = "wait")     ? 1 : wait       ; Waits for the command to execute completely before returning. Use 'exit'.

      window_width     := (p ~= "i)window" && p ~= "i)width")     ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : window_width
      window_height    := (p ~= "i)window" && p ~= "i)height")    ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : window_height
      background_color := (p ~= "i)background" && p ~= "i)color") ? RegExReplace(p, "(?i)^.*([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})\b.*$", "0x$3$2$1") : background_color
      text_color       := (p ~= "i)text" && p ~= "i)color")       ? RegExReplace(p, "(?i)^.*([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})\b.*$", "0x$3$2$1") : text_color
   }

   ; Process parameters.
   _color := background_color || text_color
   _cosmetic := window_width || window_height || background_color || text_color
   _return := stderr || stdout
   _path := (root) ? A_ScriptDir : (_path) ? _path : A_Desktop
   _win := (hide) ? "hide" : (max) ? "max" : (min) ? "min" : ""

   ; Escape double quote character.
   static q := Chr(0x22)

   ; Construct the command to execute.
   ; Workaround: A "stdout" or "stderr" console is not allowed to be elevated as admin consoles cannot be attached to user scripts.
   if (run) {
      _cmd := (admin)                                  ? "*RunAs " subroutine     : subroutine
   } else {
      _cmd .= (admin && (A_IsAdmin || !_return))       ? "*RunAs " ComSpec " /U"  : ComSpec " /U"   ; Output unicode characters.
      _cmd .= (_color)                                 ? " /T:0a "                : ""              ; Sets the default colors to be modified later.
      _cmd .= (exit && !_return)                       ? " /C "                   : " /K "          ; Terminate or return to the prompt.
      _cmd .= (admin)                                  ? "cd /d " q _path q " "   : ""              ; Manually change path if admin.
      _cmd .= (admin && subroutine != "" && !_return)  ? " && "                   : ""
      _cmd .= (subroutine != "" && !_return)           ? subroutine               : ""
      _cmd .= (pause && !_return)                      ? " && pause"              : ""
   }

   ; Show command for debugging.
   if (debug)
      MsgBox % _cmd

   ; Alter console registry keys for cosmetic purposes.
   if (_cosmetic && !run) {
      RegRead _window_size,      % "HKEY_CURRENT_USER\Console", % "WindowSize"
      RegRead _background_color, % "HKEY_CURRENT_USER\Console", % "ColorTable00"
      RegRead _text_color,       % "HKEY_CURRENT_USER\Console", % "ColorTable10"

      _backup .= "REG_DWORD,WindowSize,"   _window_size      "|"
      _backup .= "REG_DWORD,ColorTable00," _background_color "|"
      _backup .= "REG_DWORD,ColorTable10," _text_color

      ; Store original registry keys and do not overwrite existing ones.
      if !(_restore)
         RegWrite % "REG_SZ", % "HKEY_CURRENT_USER\Console", % "(Backup)", % _backup

      window_size := 0
      window_size |= (window_width) ? (window_width) : _window_size & 0xFFFF
      window_size |= (window_height) ? (window_height << 16) : _window_size & 0xFFFF0000

      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "WindowSize",   % (window_size)      ? window_size      : _window_size
      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "ColorTable00", % (background_color) ? background_color : _background_color
      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "ColorTable10", % (text_color)       ? text_color       : _text_color
   }

   ; Ensures that 32-bit AutoHotkey opens a 32-bit console.
   ; Workaround: Redirection is disabled for elevated consoles because RunAs fails to escalate in 32-bit console on 64-bit OS.
   if (!admin && A_Is64bitOS && A_PtrSize == 4)
      DllCall("Wow64DisableWow64FsRedirection", "ptr*", _oldRedirectionValue)

   ; Execute. Errors will fail silently due to "try".
   if (wait)
      try RunWait % _cmd, % _path, % _win, _pid
      catch
         return
   else
      try Run % _cmd, % _path, % _win, _pid
      catch
         return

   ; Restore SysWow64 redirection when running as 32-bit AutoHotkey on 64-bit OS.
   if (_oldRedirectionValue)
      if !DllCall("Wow64RevertWow64FsRedirection", "ptr", _oldRedirectionValue)
         throw Exception("SysWow64 redirection failed.")

   ; Allow detection of hidden consoles.
   _dhw := A_DetectHiddenWindows
   DetectHiddenWindows On

   ; Prevent a race condition.
   if (_cosmetic && !run) OR (activate) OR (bind || (_return && exit)) OR (_return && !run) {
      WinWait ahk_pid %_pid%,, 12 ; Process Wait flashes a console occasionally.
      if (ErrorLevel)
         throw Exception("The application or console has already exited cannot be found."
         . "Remove the activate, bind, and exit parameters if this error persists.")
   }

   ; Restore original registry keys.
   if (_restore || _backup) {
      Loop Parse, % (_restore) ? _restore : _backup , % "|"
      {
         ___ := StrSplit(A_LoopField, ",")
         RegWrite % ___.1, % "HKEY_CURRENT_USER\Console", % ___.2 , % ___.3
      }
      RegDelete % "HKEY_CURRENT_USER\Console", % "(Backup)"
   }

   ; Activate the console.
   ; Known Issue: An elevated console cannot be activated by a user level script. Install AutoHotkey with UI access.
   if (activate)
      WinActivate ahk_pid %_pid%

   ; Ensures that the process executed has a fail-safe method of termination.
   ; Spawns an independent process that does two things:
   ; (1) Terminates the child process when this owner process is no longer running. (e.g. crashes or exits)
   ; (2) Exits when the child process exits.
   if (bind || (_return && exit)) {
      VarSetCapacity(_process, 2048)
      DllCall("GetModuleFileName", "int", 0, "str", _process) ; Get name of current process.
      SplitPath _process,,,, _process
      ; Memory usage: Slowly climbs and stabilizes to 8196 KB from my testing.
      ;_bind := Comspec " /q /c for /L %n in (1,0,10) do (timeout /t 1 1>NUL && (tasklist /FI " q "PID eq "
      ;      . DllCall("GetCurrentProcessId") q " 2>NUL | find /I /N " q _process q " 1>NUL || TASKKILL /PID "
      ;      . _pid " /F 2>NUL) & (tasklist /FI " q "PID eq " _pid q " 2>NUL | find /I /N " q _pid q " 1>NUL || exit))"
      _bind := "powershell -NoProfile -command " q "& {Do {if (Get-Process -id " DllCall("GetCurrentProcessId")
               . " | where {$_.Processname -eq '" _process "'}) {sleep 1} else {Get-Process -id " _pid
               . " | foreach {$_.CloseMainWindow(); Stop-Process -id $_.id}}} while (Get-Process -id " _pid ")}" q
      Run % _bind,, hide
      VarSetCapacity(_process, 0)
   }

   ; If "stdout" or "stderr" is passed, attach to the console, and run the subroutine using that console host.
   if (_return && !run) {
      DllCall("AttachConsole", "uint", _pid)                  ; Attaching a hidden console prevents flashes.
      objShell := ComObjCreate("WScript.Shell")               ; Will allocate a new console if needed causing flashes.
      objExec := objShell.Exec(ComSpec " /C " q subroutine q) ; Windows 7: Don't call subroutine directly.
      while (!objExec.Status)
         Sleep 10
      _stdout := objExec.StdOut.ReadAll()
      _stderr := objExec.StdErr.ReadAll()
      DllCall("FreeConsole")
      Process Close, % _pid                                   ; Better than PostMessage and WinClose.
   }

   ; Restore global script settings.
   DetectHiddenWindows %_dhw%
   Critical Off

   ; Returns an array only if both stdout and stderr are wanted. Otherwise returns stdout, stderr, or the process ID.
   return (stdout && stderr) ? {0:_pid, 1:_stdout, 2:stderr} : (stdout) ? _stdout : (stderr) ? _stderr : _pid
}
There will be additional small feature expansions in the next month as I work through the registry settings for the console. Expect another bugfix patch in a year after I've found all the bugs.
Last edited by iseahound on 06 May 2020, 14:50, edited 2 times in total.

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: CommandRoot - Open Command Window in Current Folder

Post by iseahound » 06 May 2020, 12:30

Forgive the double post, but I'll add some demonstrations of advanced features here.

bind parameter - what does it do?

Code: Select all

Console("ping -t 8.8.8.8", "bind")
Esc:: ExitApp
By exiting the app, the console is forced to exit as well. That's because the bind parameter spawns a helper process with one goal: To close the child process when the parent process is no longer running. This could be useful if you don't want your subprocesses to run haywire. It's a fail-safe for possibly unstable commands.

run parameter - skips the command prompt and runs the program directly. Usually used with bind.

Code: Select all

; Identical
; Run notepad
; Console("notepad", "run")
Console("notepad", "run", "bind")
Esc:: ExitApp

hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: CommandRoot - Open Command Window in Current Folder

Post by hasantr » 10 May 2020, 11:01

iseahound wrote:
06 May 2020, 12:11
2020-05-06 New Features and bugfixes
  • Renamed from CommandRoot() to Console()
  • NEW: Change background and text color using RGB values. Example: Console("powershell", "background color #259184", "text color #FFFFFF")
  • NEW: Change width and height of console. Example: Console("powershell", "background color #259184", "text color #FFFFFF", "window width 200", "window height 10")
  • FEATURE: Automatically detect and restore corrupted registry keys if execution fails on next run.
  • FEATURE: The activate parameter will activate the window.
  • FEATURE: The debug parameter shows the command before it is executed.
  • BUGFIX: A user level script can no longer launch an administrator console with stderr or stdout enabled.
  • BUGFIX: Launching multiple consoles using stderr or stdout now properly closes the consoles when finished.
  • BUGFIX: Process is now Critical to ensure that it cannot be interrupted by repeated hotkey presses.
  • BUGFIX: Removed race conditions involved with bind.
  • BUGFIX: When launching a administrator console and the user presses no at the UAC prompt, the script now exits properly and returns an empty string.
  • BUGFIX: Windows 7 compatibility has been restored with stderr and stdout.
  • REMOVED: Old color syntax like color 0a
2020-05-06 Hotfix a bug where in case of corruption the restoration of original registry keys would fail.

Code: Select all

; Return values:
;    pid                 | Process ID of the launched process if successful.
;    stdout              | Standard output if "stdout" is a parameter.
;    stderr              | Standard error if "stderr" is a parameter.
;    [pid,stdout,stderr] | An array at indexes 0, 1, and 2 if both "stdout" and "stderr" are input parameters.
;    ""                  | Error.

Console(subroutine := "", parameters*) {
   Critical On ; Ensure that sub processes are closed properly.

   ; Get path of active window.
   _hwnd := WinExist("A")
   WinGetClass _class, ahk_id %_hwnd%
   if (_class == "ExploreWClass" || _class == "CabinetWClass")
      for window in ComObjCreate("Shell.Application").Windows
         if (window.hwnd == _hwnd)
            _path := try window.Document.Folder.Self.Path

   ; Check for original registry keys in case this script crashed during a previous execution.
   RegRead _restore, % "HKEY_CURRENT_USER\Console", % "(Backup)"

   ; Parse parameters.
   for i, p in parameters {
      activate := (p = "activate") ? 1 : activate   ; Activates the console.
      admin    := (p = "admin")    ? 1 : admin      ; Elevates to administrator. Shows UAC prompt.
      bind     := (p = "bind")     ? 1 : bind       ; Forcefully quits all child processes when current script crashes or ends.
      debug    := (p = "debug")    ? 1 : debug      ; Shows the command to be run.
      exit     := (p = "exit")     ? 1 : exit       ; Runs command and then exits the console.
      hide     := (p = "hide")     ? 1 : hide       ; Hides the console.
      max      := (p ~= "i)^max")  ? 1 : max        ; Maximizes the console.
      min      := (p ~= "i)^min")  ? 1 : min        ; Minimizes the console.
      pause    := (p = "pause")    ? 1 : pause      ; Pauses the console after execution. Useful with 'exit'.
      root     := (p = "root")     ? 1 : root       ; Launches from the script's current directory.
      run      := (p = "run")      ? 1 : run        ; Bypasses using a console and runs the executable directly.
      stderr   := (p = "stderr")   ? 1 : stderr     ; Returns the standard error. (If stderr & stdout are both selected an array will be returned.)
      stdout   := (p = "stdout")   ? 1 : stdout     ; Returns the standard output. (array[1] is stdout and array[2] is stderr.)
      wait     := (p = "wait")     ? 1 : wait       ; Waits for the command to execute completely before returning. Use 'exit'.

      window_width     := (p ~= "i)window" && p ~= "i)width")     ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : window_width
      window_height    := (p ~= "i)window" && p ~= "i)height")    ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : window_height
      background_color := (p ~= "i)background" && p ~= "i)color") ? RegExReplace(p, "(?i)^.*([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})\b.*$", "0x$3$2$1") : background_color
      text_color       := (p ~= "i)text" && p ~= "i)color")       ? RegExReplace(p, "(?i)^.*([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})\b.*$", "0x$3$2$1") : text_color
   }

   ; Process parameters.
   _color := background_color || text_color
   _cosmetic := window_width || window_height || background_color || text_color
   _return := stderr || stdout
   _path := (root) ? A_ScriptDir : (_path) ? _path : A_Desktop
   _win := (hide) ? "hide" : (max) ? "max" : (min) ? "min" : ""

   ; Escape double quote character.
   static q := Chr(0x22)

   ; Construct the command to execute.
   ; Workaround: A "stdout" or "stderr" console is not allowed to be elevated as admin consoles cannot be attached to user scripts.
   if (run) {
      _cmd := (admin)                                  ? "*RunAs " subroutine     : subroutine
   } else {
      _cmd .= (admin && (A_IsAdmin || !_return))       ? "*RunAs " ComSpec " /U"  : ComSpec " /U"   ; Output unicode characters.
      _cmd .= (_color)                                 ? " /T:0a "                : ""              ; Sets the default colors to be modified later.
      _cmd .= (exit && !_return)                       ? " /C "                   : " /K "          ; Terminate or return to the prompt.
      _cmd .= (admin)                                  ? "cd /d " q _path q " "   : ""              ; Manually change path if admin.
      _cmd .= (admin && subroutine != "" && !_return)  ? " && "                   : ""
      _cmd .= (subroutine != "" && !_return)           ? subroutine               : ""
      _cmd .= (pause && !_return)                      ? " && pause"              : ""
   }

   ; Show command for debugging.
   if (debug)
      MsgBox % _cmd

   ; Alter console registry keys for cosmetic purposes.
   if (_cosmetic && !run) {
      RegRead _window_size,      % "HKEY_CURRENT_USER\Console", % "WindowSize"
      RegRead _background_color, % "HKEY_CURRENT_USER\Console", % "ColorTable00"
      RegRead _text_color,       % "HKEY_CURRENT_USER\Console", % "ColorTable10"

      _backup .= "REG_DWORD,WindowSize,"   _window_size      "|"
      _backup .= "REG_DWORD,ColorTable00," _background_color "|"
      _backup .= "REG_DWORD,ColorTable10," _text_color

      ; Store original registry keys and do not overwrite existing ones.
      if !(_restore)
         RegWrite % "REG_SZ", % "HKEY_CURRENT_USER\Console", % "(Backup)", % _backup

      window_size := 0
      window_size |= (window_width) ? (window_width) : _window_size & 0xFFFF
      window_size |= (window_height) ? (window_height << 16) : _window_size & 0xFFFF0000

      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "WindowSize",   % (window_size)      ? window_size      : _window_size
      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "ColorTable00", % (background_color) ? background_color : _background_color
      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "ColorTable10", % (text_color)       ? text_color       : _text_color
   }

   ; Ensures that 32-bit AutoHotkey opens a 32-bit console.
   ; Workaround: Redirection is disabled for elevated consoles because RunAs fails to escalate in 32-bit console on 64-bit OS.
   if (!admin && A_Is64bitOS && A_PtrSize == 4)
      DllCall("Wow64DisableWow64FsRedirection", "ptr*", _oldRedirectionValue)

   ; Execute. Errors will fail silently due to "try".
   if (wait)
      try RunWait % _cmd, % _path, % _win, _pid
      catch
         return
   else
      try Run % _cmd, % _path, % _win, _pid
      catch
         return

   ; Restore SysWow64 redirection when running as 32-bit AutoHotkey on 64-bit OS.
   if (_oldRedirectionValue)
      if !DllCall("Wow64RevertWow64FsRedirection", "ptr", _oldRedirectionValue)
         throw Exception("SysWow64 redirection failed.")

   ; Allow detection of hidden consoles.
   _dhw := A_DetectHiddenWindows
   DetectHiddenWindows On

   ; Prevent a race condition.
   if (_cosmetic && !run) OR (activate) OR (bind || (_return && exit)) OR (_return && !run) {
      WinWait ahk_pid %_pid%,, 12 ; Process Wait flashes a console occasionally.
      if (ErrorLevel)
         throw Exception("The application or console has already exited cannot be found."
         . "Remove the activate, bind, and exit parameters if this error persists.")
   }

   ; Restore original registry keys.
   if (_restore || _backup) {
      Loop Parse, % (_restore) ? _restore : _backup , % "|"
      {
         ___ := StrSplit(A_LoopField, ",")
         RegWrite % ___.1, % "HKEY_CURRENT_USER\Console", % ___.2 , % ___.3
      }
      RegDelete % "HKEY_CURRENT_USER\Console", % "(Backup)"
   }

   ; Activate the console.
   ; Known Issue: An elevated console cannot be activated by a user level script. Install AutoHotkey with UI access.
   if (activate)
      WinActivate ahk_pid %_pid%

   ; Ensures that the process executed has a fail-safe method of termination.
   ; Spawns an independent process that does two things:
   ; (1) Terminates the child process when this owner process is no longer running. (e.g. crashes or exits)
   ; (2) Exits when the child process exits.
   if (bind || (_return && exit)) {
      VarSetCapacity(_process, 2048)
      DllCall("GetModuleFileName", "int", 0, "str", _process) ; Get name of current process.
      SplitPath _process,,,, _process
      ; Memory usage: Slowly climbs and stabilizes to 8196 KB from my testing.
      ;_bind := Comspec " /q /c for /L %n in (1,0,10) do (timeout /t 1 1>NUL && (tasklist /FI " q "PID eq "
      ;      . DllCall("GetCurrentProcessId") q " 2>NUL | find /I /N " q _process q " 1>NUL || TASKKILL /PID "
      ;      . _pid " /F 2>NUL) & (tasklist /FI " q "PID eq " _pid q " 2>NUL | find /I /N " q _pid q " 1>NUL || exit))"
      _bind := "powershell -NoProfile -command " q "& {Do {if (Get-Process -id " DllCall("GetCurrentProcessId")
               . " | where {$_.Processname -eq '" _process "'}) {sleep 1} else {Get-Process -id " _pid
               . " | foreach {$_.CloseMainWindow(); Stop-Process -id $_.id}}} while (Get-Process -id " _pid ")}" q
      Run % _bind,, hide
      VarSetCapacity(_process, 0)
   }

   ; If "stdout" or "stderr" is passed, attach to the console, and run the subroutine using that console host.
   if (_return && !run) {
      DllCall("AttachConsole", "uint", _pid)                  ; Attaching a hidden console prevents flashes.
      objShell := ComObjCreate("WScript.Shell")               ; Will allocate a new console if needed causing flashes.
      objExec := objShell.Exec(ComSpec " /C " q subroutine q) ; Windows 7: Don't call subroutine directly.
      while (!objExec.Status)
         Sleep 10
      _stdout := objExec.StdOut.ReadAll()
      _stderr := objExec.StdErr.ReadAll()
      DllCall("FreeConsole")
      Process Close, % _pid                                   ; Better than PostMessage and WinClose.
   }

   ; Restore global script settings.
   DetectHiddenWindows %_dhw%
   Critical Off

   ; Returns an array only if both stdout and stderr are wanted. Otherwise returns stdout, stderr, or the process ID.
   return (stdout && stderr) ? {0:_pid, 1:_stdout, 2:stderr} : (stdout) ? _stdout : (stderr) ? _stderr : _pid
}
There will be additional small feature expansions in the next month as I work through the registry settings for the console. Expect another bugfix patch in a year after I've found all the bugs.
Thanks looks pretty safe. it works very well and it's very simple.
Is it possible to print on Autohotkey Gui TextBox instead of cmd during ping?
I want to be able to hide the cdm stream and watch it on the TexBox.



iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Console() - Customize your command prompt

Post by iseahound » 05 Feb 2021, 19:47

Update.

Code: Select all

PowerShell(subroutine, parameters*) {
   return Console("powershell -NoProfile -command " Chr(34) "& {" subroutine "}" Chr(34), parameters*)
}

; Return values:
;    positive integer    | Process ID of the launched process if successful.
;    ""                  | Error.
;    stdout              | Standard output if "stdout" is a parameter.
;    stderr              | Standard error if "stderr" is a parameter.
;    [pid,stdout,stderr] | An array at indexes 0, 1, and 2 if both "stdout" and "stderr" are input parameters.

Console(subroutine := "", parameters*) {
   Critical On ; Ensure that sub processes are closed properly.

   ; Get path of active window.
   _hwnd := WinExist("A")
   WinGetClass _class, ahk_id %_hwnd%
   if (_class == "ExploreWClass" || _class == "CabinetWClass")
      for window in ComObjCreate("Shell.Application").Windows
         if (window.hwnd == _hwnd)
            _path := try window.Document.Folder.Self.Path

   ; Parse parameters.
   for i, p in parameters {
      activate   := (p = "activate")   ? 1 : activate   ; Activates the console.
      admin      := (p = "admin")      ? 1 : admin      ; Elevates to administrator. Shows UAC prompt.
      bind       := (p = "bind")       ? 1 : bind       ; Forcefully quits all child processes when current script crashes or ends.
      cd         := (p = "cd")         ? 1 : cd         ; Launches from the script's current directory.
      debug      := (p = "debug")      ? 1 : debug      ; Shows the command to be run.
      exit       := (p = "exit")       ? 1 : exit       ; Runs command and then exits the console.
      hide       := (p = "hide")       ? 1 : hide       ; Hides the console.
      max        := (p ~= "i)^max")    ? 1 : max        ; Maximizes the console.
      min        := (p ~= "i)^min")    ? 1 : min        ; Minimizes the console.
      pause      := (p = "pause")      ? 1 : pause      ; Pauses the console after execution. Useful with 'exit'.
      run        := (p = "run")        ? 1 : run        ; Bypasses using a console and runs the executable directly.
      stderr     := (p = "stderr")     ? 1 : stderr     ; Returns the standard error. (If stderr & stdout are both selected an array will be returned.)
      stdout     := (p = "stdout")     ? 1 : stdout     ; Returns the standard output. (array[1] is stdout and array[2] is stderr.)
      wait       := (p = "wait")       ? 1 : wait       ; Waits for the command to execute completely before returning. Use 'exit'.
      working    := (p = "working")    ? 1 : working    ; Launches from the script's working directory.

      window_width     := (p ~= "i)window" && p ~= "i)width")     ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : window_width
      window_height    := (p ~= "i)window" && p ~= "i)height")    ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : window_height
      background_color := (p ~= "i)background" && p ~= "i)color") ? RegExReplace(p, "(?i)^.*([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})\b.*$", "0x$3$2$1") : background_color
      text_color       := (p ~= "i)text" && p ~= "i)color")       ? RegExReplace(p, "(?i)^.*([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})\b.*$", "0x$3$2$1") : text_color
      face_name        := (p ~= "i)face" && p ~= "i)name")        ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : face_name
   }

   ; Process parameters.
   _color := background_color || text_color
   _cosmetic := window_width || window_height || background_color || text_color
   _output := stderr || stdout
   _path := (cd) ? A_ScriptDir : (working) ? A_WorkingDir : (_path) ? _path : A_Desktop
   _win := (hide) ? "hide" : (max) ? "max" : (min) ? "min" : ""

   ; Escape double quote character.
   static q := Chr(0x22)

   ; Construct the command to execute.
   ; Workaround: The flags "stdout" or "stderr" will suppress the "admin" flag when run from a user level script or else "AttachConsole" would fail.
   if (run)
      _cmd := (!A_IsAdmin && admin)        ? "*RunAs " subroutine       : subroutine
   else if (_output)
      _cmd := (_color)                     ? A_ComSpec " /U /K"         : A_ComSpec " /U /T:07 /K"
   else {
      _cmd .= (!A_IsAdmin && admin)        ? "*RunAs " A_ComSpec " /U " : A_ComSpec " /U "  ; Output unicode characters.
      _cmd .= (_color)                     ? "/T:07 "                   : ""                ; Sets the default colors to be modified later.
      _cmd .= (exit)                       ? "/C "                      : "/K "             ; Terminate or return to the prompt.
      _cmd .= (admin)                      ? "cd /d " q _path q " "     : ""                ; Manually change path if admin.
      _cmd .= (admin && subroutine != "")  ? "&& "                      : ""
      _cmd .= (subroutine != "")           ? subroutine                 : ""
      _cmd .= (pause)                      ? " && pause"                : ""
   }

   ; Show command for debugging.
   if (debug)
      MsgBox % _cmd

   ; Check for original registry keys in case this script crashed during a previous execution.
   RegRead _restore, HKCU\Console, % "(Backup)"

   ; Alter console registry keys for cosmetic purposes. https://devblogs.microsoft.com/commandline/understanding-windows-console-host-settings/
   if (_cosmetic && !run) {
      RegRead _window_size,      HKCU\Console, WindowSize
      RegRead _background_color, HKCU\Console, ColorTable00
      RegRead _text_color,       HKCU\Console, ColorTable07

      _backup .= "REG_DWORD,WindowSize,"    _window_size       "|"
      _backup .= "REG_DWORD,ColorTable00,"  _background_color  "|"
      _backup .= "REG_DWORD,ColorTable07,"  _text_color        "|"

      ; Store original registry keys and do not overwrite existing ones.
      if !(_restore)
         RegWrite % "REG_SZ", HKCU\Console, % "(Backup)", % _backup

      window_size := 0
      window_size |= (window_width) ? (window_width) : _window_size & 0xFFFF
      window_size |= (window_height) ? (window_height << 16) : _window_size & 0xFFFF0000

      RegWrite REG_DWORD, HKCU\Console, WindowSize,   % (window_size)      ? window_size      : _window_size
      RegWrite REG_DWORD, HKCU\Console, ColorTable00, % (background_color) ? background_color : _background_color
      RegWrite REG_DWORD, HKCU\Console, ColorTable07, % (text_color)       ? text_color       : _text_color
   }

   ; Ensures that 32-bit AutoHotkey opens a 32-bit console.
   ; Workaround: Redirection is disabled for elevated consoles because RunAs fails to escalate in 32-bit console on 64-bit OS.
   if (!admin && A_Is64bitOS && A_PtrSize == 4)
      DllCall("Wow64DisableWow64FsRedirection", "ptr*", _oldRedirectionValue)

   ; Execute. Errors will fail silently due to "try".
   if (wait)
      try RunWait % _cmd, % _path, % _win, _pid
      catch
         return
   else
      try Run % _cmd, % _path, % _win, _pid
      catch
         return

   ; Restore SysWow64 redirection when running as 32-bit AutoHotkey on 64-bit OS.
   if (_oldRedirectionValue)
      if !DllCall("Wow64RevertWow64FsRedirection", "ptr", _oldRedirectionValue)
         throw Exception("SysWow64 redirection failed.")

   ; Allow detection of hidden consoles.
   _dhw := A_DetectHiddenWindows
   DetectHiddenWindows On

   ; Prevent a race condition.
   if (_cosmetic && !run) OR (activate) OR (bind || (_output && exit)) OR (_output && !run) {
      WinWait ahk_pid %_pid%,, 12 ; Process Wait flashes a console occasionally.
      if (ErrorLevel)
         throw Exception("The application or console has already exited cannot be found."
         . "Remove the activate, bind, and exit parameters if this error persists.")
   }

   ; Restore original registry keys. Don't restore before window appears.
   if (_restore || _backup) {
      Loop Parse, % (_restore) ? _restore : _backup , % "|"
      {
         ___ := StrSplit(A_LoopField, ",")
         RegWrite % ___.1, HKCU\Console, % ___.2 , % ___.3
      }
      RegDelete HKCU\Console, % "(Backup)"
   }

   ; Activate the console.
   ; Known Issue: An elevated console cannot be activated by a user level script. Install AutoHotkey with UI access.
   if (activate)
      WinActivate ahk_pid %_pid%

   ; Ensures that the process executed has a fail-safe method of termination.
   ; Spawns an independent process that does two things:
   ; (1) Terminates the child process when this owner process is no longer running. (e.g. crashes or exits)
   ; (2) Exits when the child process exits.
   if (bind || (_output && exit)) {
      VarSetCapacity(_process, 2048)
      DllCall("GetModuleFileName", "int", 0, "str", _process) ; Get name of current process.
      SplitPath _process,,,, _process
      ; Memory usage: Slowly climbs and stabilizes to 8196 KB from my testing.
      ;_bind := Comspec " /q /c for /L %n in (1,0,10) do (timeout /t 1 1>NUL && (tasklist /FI " q "PID eq "
      ;      . DllCall("GetCurrentProcessId") q " 2>NUL | find /I /N " q _process q " 1>NUL || TASKKILL /PID "
      ;      . _pid " /F 2>NUL) & (tasklist /FI " q "PID eq " _pid q " 2>NUL | find /I /N " q _pid q " 1>NUL || exit))"
      _bind := "powershell -NoProfile -command " q "& {Do {if (Get-Process -id " DllCall("GetCurrentProcessId")
               . " | where {$_.Processname -eq '" _process "'}) {sleep 1} else {Get-Process -id " _pid
               . " | foreach {$_.CloseMainWindow(); Stop-Process -id $_.id}}} while (Get-Process -id " _pid ")}" q
      Run % _bind,, hide
      VarSetCapacity(_process, 0)
   }

   ; If "stdout" or "stderr" is passed, attach to the console, and run the subroutine using that console host.
   if (_output && !run) {
      DllCall("AttachConsole", "uint", _pid)                  ; Attaching a hidden console prevents flashes.
      objShell := ComObjCreate("WScript.Shell")               ; Will allocate a new console if needed causing flashes.
      objExec := objShell.Exec(ComSpec " /C " q subroutine q) ; Windows 7: Don't call subroutine directly.
      while (!objExec.Status)
         Sleep 10
      _stdout := objExec.StdOut.ReadAll()
      _stderr := objExec.StdErr.ReadAll()
      DllCall("FreeConsole")
      Process Close, % _pid                                   ; Better than PostMessage and WinClose.
   }

   ; Restore global script settings.
   DetectHiddenWindows %_dhw%
   Critical Off

   ; Returns an array only if both stdout and stderr are wanted. Otherwise returns stdout, stderr, or the process ID.
   return (stdout && stderr) ? {0:_pid, 1:_stdout, 2:stderr} : (stdout) ? _stdout : (stderr) ? _stderr : _pid
}

ElectroLund
Posts: 3
Joined: 17 Nov 2020, 12:14

Re: Console() - Customize your command prompt

Post by ElectroLund » 05 Oct 2022, 07:54

Great script! I love how this greatly simplifies my use and interaction with a console instance.

Any reason why the window width and height parameters aren't working for me? I've tried using the example calls and those two are ignored. text and background colors, other parameters are obeyed.

User avatar
joedf
Posts: 8953
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: Console() - Customize your command prompt

Post by joedf » 05 Oct 2022, 08:11

Wow this is very neat! Definitely could have used this when I was working on LibCon.ahk and Qonsole! :+1:
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]

ElectroLund
Posts: 3
Joined: 17 Nov 2020, 12:14

Re: Console() - Customize your command prompt

Post by ElectroLund » 05 Oct 2022, 10:57

I had a problem with this library, where the console size parameters weren't being used. Come to find out, my registry had some strange keys that were overwriting the keys that your library writes to.
2022-10-05 08_05_03-Registry Editor.png
2022-10-05 08_05_03-Registry Editor.png (41.51 KiB) Viewed 1455 times
I simply removed those extra sub branches and now it's working fine.

I also updated the library to allow for custom font sizing:

Code: Select all

; Return values:
;    pid                 | Process ID of the launched process if successful.
;    stdout              | Standard output if "stdout" is a parameter.
;    stderr              | Standard error if "stderr" is a parameter.
;    [pid,stdout,stderr] | An array at indexes 0, 1, and 2 if both "stdout" and "stderr" are input parameters.
;    ""                  | Error.

Console(subroutine := "", parameters*) {
   Critical On ; Ensure that sub processes are closed properly.

   ; Get path of active window.
   _hwnd := WinExist("A")
   WinGetClass _class, ahk_id %_hwnd%
   if (_class == "ExploreWClass" || _class == "CabinetWClass")
      for window in ComObjCreate("Shell.Application").Windows
         if (window.hwnd == _hwnd)
            _path := try window.Document.Folder.Self.Path

   ; Check for original registry keys in case this script crashed during a previous execution.
   RegRead _restore, % "HKEY_CURRENT_USER\Console", % "(Backup)"

   ; Parse parameters.
   for i, p in parameters {
      activate := (p = "activate") ? 1 : activate   ; Activates the console.
      admin    := (p = "admin")    ? 1 : admin      ; Elevates to administrator. Shows UAC prompt.
      bind     := (p = "bind")     ? 1 : bind       ; Forcefully quits all child processes when current script crashes or ends.
      debug    := (p = "debug")    ? 1 : debug      ; Shows the command to be run.
      exit     := (p = "exit")     ? 1 : exit       ; Runs command and then exits the console.
      hide     := (p = "hide")     ? 1 : hide       ; Hides the console.
      max      := (p ~= "i)^max")  ? 1 : max        ; Maximizes the console.
      min      := (p ~= "i)^min")  ? 1 : min        ; Minimizes the console.
      pause    := (p = "pause")    ? 1 : pause      ; Pauses the console after execution. Useful with 'exit'.
      root     := (p = "root")     ? 1 : root       ; Launches from the script's current directory.
      run      := (p = "run")      ? 1 : run        ; Bypasses using a console and runs the executable directly.
      stderr   := (p = "stderr")   ? 1 : stderr     ; Returns the standard error. (If stderr & stdout are both selected an array will be returned.)
      stdout   := (p = "stdout")   ? 1 : stdout     ; Returns the standard output. (array[1] is stdout and array[2] is stderr.)
      wait     := (p = "wait")     ? 1 : wait       ; Waits for the command to execute completely before returning. Use 'exit'.

      window_width     := (p ~= "i)window" && p ~= "i)width")     ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : window_width
      window_height    := (p ~= "i)window" && p ~= "i)height")    ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : window_height
      background_color := (p ~= "i)background" && p ~= "i)color") ? RegExReplace(p, "(?i)^.*([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})\b.*$", "0x$3$2$1") : background_color
      text_color       := (p ~= "i)text" && p ~= "i)color")       ? RegExReplace(p, "(?i)^.*([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})\b.*$", "0x$3$2$1") : text_color
      text_height      := (p ~= "i)text" && p ~= "i)size")        ? RegExReplace(p, "^.*(?<!\d)(\d+).*$", "$1")                                     : text_height
   }

   ; Process parameters.
   _color := background_color || text_color
   _cosmetic := window_width || window_height || background_color || text_color || text_height
   _return := stderr || stdout
   _path := (root) ? A_ScriptDir : (_path) ? _path : A_Desktop
   _win := (hide) ? "hide" : (max) ? "max" : (min) ? "min" : ""

   ; Escape double quote character.
   static q := Chr(0x22)

   ; Construct the command to execute.
   ; Workaround: A "stdout" or "stderr" console is not allowed to be elevated as admin consoles cannot be attached to user scripts.
   if (run) {
      _cmd := (admin)                                  ? "*RunAs " subroutine     : subroutine
   } else {
      _cmd .= (admin && (A_IsAdmin || !_return))       ? "*RunAs " ComSpec " /U"  : ComSpec " /U"   ; Output unicode characters.
      _cmd .= (_color)                                 ? " /T:0a "                : ""              ; Sets the default colors to be modified later.
      _cmd .= (exit && !_return)                       ? " /C "                   : " /K "          ; Terminate or return to the prompt.
      _cmd .= (admin)                                  ? "cd /d " q _path q " "   : ""              ; Manually change path if admin.
      _cmd .= (admin && subroutine != "" && !_return)  ? " && "                   : ""
      _cmd .= (subroutine != "" && !_return)           ? subroutine               : ""
      _cmd .= (pause && !_return)                      ? " && pause"              : ""
   }

   ; Show command for debugging.
   if (debug)
      MsgBox % _cmd

   ; Alter console registry keys for cosmetic purposes.
   if (_cosmetic && !run) {
      RegRead _window_size,      % "HKEY_CURRENT_USER\Console", % "WindowSize"
      RegRead _text_size,        % "HKEY_CURRENT_USER\Console", % "FontSize"
      RegRead _background_color, % "HKEY_CURRENT_USER\Console", % "ColorTable00"
      RegRead _text_color,       % "HKEY_CURRENT_USER\Console", % "ColorTable10"

      _backup .= "REG_DWORD,WindowSize,"   _window_size        "|"
      _backup .= "REG_DWORD,FontSize,"     _text_size          "|"
      _backup .= "REG_DWORD,ColorTable00," _background_color   "|"
      _backup .= "REG_DWORD,ColorTable10," _text_color

      ; Store original registry keys and do not overwrite existing ones.
      if !(_restore)
         RegWrite % "REG_SZ", % "HKEY_CURRENT_USER\Console", % "(Backup)", % _backup

      window_size := 0
      window_size |= (window_width) ? (window_width) : _window_size & 0xFFFF
      window_size |= (window_height) ? (window_height << 16) : _window_size & 0xFFFF0000

      ; for font size, it's safe enough to use only the height, with a 0 width
      text_size := 0
      text_size |= (text_height) ? (text_height << 16) : _text_size & 0xFFFF0000

      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "WindowSize",   % (window_size)      ? window_size      : _window_size
      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "ColorTable00", % (background_color) ? background_color : _background_color
      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "ColorTable10", % (text_color)       ? text_color       : _text_color
      RegWrite % "REG_DWORD", % "HKEY_CURRENT_USER\Console", % "FontSize",     % (text_size)        ? text_size        : _text_size
   }

   ; Ensures that 32-bit AutoHotkey opens a 32-bit console.
   ; Workaround: Redirection is disabled for elevated consoles because RunAs fails to escalate in 32-bit console on 64-bit OS.
   if (!admin && A_Is64bitOS && A_PtrSize == 4)
      DllCall("Wow64DisableWow64FsRedirection", "ptr*", _oldRedirectionValue)

   ; Execute. Errors will fail silently due to "try".
   if (wait)
      try RunWait % _cmd, % _path, % _win, _pid
      catch
         return
   else
      try Run % _cmd, % _path, % _win, _pid
      catch
         return

   ; Restore SysWow64 redirection when running as 32-bit AutoHotkey on 64-bit OS.
   if (_oldRedirectionValue)
      if !DllCall("Wow64RevertWow64FsRedirection", "ptr", _oldRedirectionValue)
         throw Exception("SysWow64 redirection failed.")

   ; Allow detection of hidden consoles.
   _dhw := A_DetectHiddenWindows
   DetectHiddenWindows On

   ; Prevent a race condition.
   if (_cosmetic && !run) OR (activate) OR (bind || (_return && exit)) OR (_return && !run) {
      WinWait ahk_pid %_pid%,, 12 ; Process Wait flashes a console occasionally.
      if (ErrorLevel)
         throw Exception("The application or console has already exited cannot be found."
         . "Remove the activate, bind, and exit parameters if this error persists.")
   }

   ; Restore original registry keys.
   if (_restore || _backup) {
      Loop Parse, % (_restore) ? _restore : _backup , % "|"
      {
         ___ := StrSplit(A_LoopField, ",")
         RegWrite % ___.1, % "HKEY_CURRENT_USER\Console", % ___.2 , % ___.3
      }
      RegDelete % "HKEY_CURRENT_USER\Console", % "(Backup)"
   }

   ; Activate the console.
   ; Known Issue: An elevated console cannot be activated by a user level script. Install AutoHotkey with UI access.
   if (activate)
      WinActivate ahk_pid %_pid%

   ; Ensures that the process executed has a fail-safe method of termination.
   ; Spawns an independent process that does two things:
   ; (1) Terminates the child process when this owner process is no longer running. (e.g. crashes or exits)
   ; (2) Exits when the child process exits.
   if (bind || (_return && exit)) {
      VarSetCapacity(_process, 2048)
      DllCall("GetModuleFileName", "int", 0, "str", _process) ; Get name of current process.
      SplitPath _process,,,, _process
      ; Memory usage: Slowly climbs and stabilizes to 8196 KB from my testing.
      ;_bind := Comspec " /q /c for /L %n in (1,0,10) do (timeout /t 1 1>NUL && (tasklist /FI " q "PID eq "
      ;      . DllCall("GetCurrentProcessId") q " 2>NUL | find /I /N " q _process q " 1>NUL || TASKKILL /PID "
      ;      . _pid " /F 2>NUL) & (tasklist /FI " q "PID eq " _pid q " 2>NUL | find /I /N " q _pid q " 1>NUL || exit))"
      _bind := "powershell -NoProfile -command " q "& {Do {if (Get-Process -id " DllCall("GetCurrentProcessId")
               . " | where {$_.Processname -eq '" _process "'}) {sleep 1} else {Get-Process -id " _pid
               . " | foreach {$_.CloseMainWindow(); Stop-Process -id $_.id}}} while (Get-Process -id " _pid ")}" q
      Run % _bind,, hide
      VarSetCapacity(_process, 0)
   }

   ; If "stdout" or "stderr" is passed, attach to the console, and run the subroutine using that console host.
   if (_return && !run) {
      DllCall("AttachConsole", "uint", _pid)                  ; Attaching a hidden console prevents flashes.
      objShell := ComObjCreate("WScript.Shell")               ; Will allocate a new console if needed causing flashes.
      objExec := objShell.Exec(ComSpec " /C " q subroutine q) ; Windows 7: Don't call subroutine directly.
      while (!objExec.Status)
         Sleep 10
      _stdout := objExec.StdOut.ReadAll()
      _stderr := objExec.StdErr.ReadAll()
      DllCall("FreeConsole")
      Process Close, % _pid                                   ; Better than PostMessage and WinClose.
   }

   ; Restore global script settings.
   DetectHiddenWindows %_dhw%
   Critical Off

   ; Returns an array only if both stdout and stderr are wanted. Otherwise returns stdout, stderr, or the process ID.
   return (stdout && stderr) ? {0:_pid, 1:_stdout, 2:stderr} : (stdout) ? _stdout : (stderr) ? _stderr : _pid
}

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Console() - Customize your command prompt

Post by iseahound » 05 Oct 2022, 12:36

Hi! I'm glad you like the script. Feel free to make any changes or modifications. I also have a working version for AutoHotkey v2, but it's very ugly code littered with IsSet().

The window width and window height never worked for me, and I just left it at that. Good problem solving 👍

All I can say is choose text_height or text_size to be consistent with naming.

Post Reply

Return to “Scripts and Functions (v1)”