11

I'm trying to read Windows CMD's stdout with AutoHotkey. For example, I'd like to have the output of the setconsole command inside AHK stored in a variable. I already achieved it a while ago, which makes me all the more perplex why it's not working now.
In the AHK forums, there's a rather old thread about CMDret, a DLL based functionality to do exactly what I want. The first problem was to find a working download for it, since all the links in the post were dead. Google gave me another site, hosting v3.1.2. Altough there seems to be a newer one (v3.2.1 respectively 4d Beta), I checked it out and tested a simple example:

msgbox % CMDret(COMSPEC " /C set")
CMDret(CMD) 
{ 
  VarSetCapacity(StrOut, 10000) 
  RetVal := DllCall("cmdret.dll\RunReturn", "str", CMD, "str", StrOut)
  Return, %StrOut%
}

Unfortunately, the MsgBox contained nothing. I then checked out RetVal which had a value of 0; and the attached readme says:

If the function fails, the return value is zero.

Further down, it says:

Note: only 32 bit console applications will currently work with the this dll version of CMDret (v3.1.2 or lower). Calls that require command.com will likely not produce any output and may crash. To avoid this I have included a file named "cmdstub.exe" with the download (in the Win9x folder). This file should be used when calling 16 bit console applications to enable returning output.

In conclusion, I am not sure what the problem is. My machine is running on 64 bit. But is the corresponding clause in the readme supposed to solely exclude 16 bit systems or does it rather only include 32 bit?
If the computing architecture is probably not the problem, then what could be?

What I am looking for is either one of the following:

  1. Can I fix the problem and keep using v3.1.2?
  2. Has anyone a working source (or even a local copy) of a newer version I could check out?
  3. Is there another approach [library, .ahk code, etc.] I could use for my purpose? (preferably similar, because CMDret seems very straightforward)
0

5 Answers 5

9

New recommended 2 ways of doing as of Nov 2019 - https://www.autohotkey.com/docs/FAQ.htm#output:

Method 1: via temporary file

Testing shows that due to file caching, a temporary file can be very fast for relatively small outputs. In fact, if the file is deleted immediately after use, it often does not actually get written to disk. For example:

RunWait %ComSpec% /c dir > C:\My Temp File.txt
FileRead, VarToContainContents, C:\My Temp File.txt
FileDelete, C:\My Temp File.txt

Method 2: via Shell.Exec()

The same doc section goes on as follows:

To avoid using a temporary file (especially if the output is large), consider using the Shell.Exec() method as shown in the examples for the Run command.

And you'll find the example mentioned on the documentation for the Run command:

MsgBox % RunWaitOne("dir " A_ScriptDir)
RunWaitOne(command) {
    shell := ComObjCreate("WScript.Shell")
    exec := shell.Exec(ComSpec " /C " command)
    return exec.StdOut.ReadAll()
}

Note: method 2 (shell.Exec) will cause quick display of a cmd window. You can reduce the duration of its appearance by putting these lines at the top of your script, which will also cause the flickering to happen only once the first time you call the cmd command. From https://autohotkey.com/board/topic/92032-how-to-hide-a-wscript-comspec-window/:

;The 2 calls below: the cmd window will flash once quickly at first run
;This is to mitigate the flickering every time we call shell.Exec
DllCall("AllocConsole")
WinHide % "ahk_id " DllCall("GetConsoleWindow", "ptr")
Sign up to request clarification or add additional context in comments.

Comments

6

Just an update to @amynbe answer.

MsgBox % RunWaitOne("dir " A_ScriptDir)


RunWaitOne(command) {
    shell := ComObjCreate("WScript.Shell")
    exec := shell.Exec(ComSpec " /C " command)
    return exec.StdOut.ReadAll()
}

Note: the latter method (shell.Exec) will cause quick display of a cmd window. You can reduce > the duration of its appearance by putting these lines at the top of > your script, which will also cause the flickering to happen only once > the first time you call the cmd command.

You can just do this below to hide cmd and avoid flashing.

v1

RunWaitOne(command) {
    DetectHiddenWindows On
    Run %ComSpec%,, Hide, pid
    WinWait ahk_pid %pid%
    DllCall("AttachConsole", "UInt", pid)
     
    shell := ComObjCreate("WScript.Shell")
    exec := shell.Exec(ComSpec " /C " command)
    result := exec.StdOut.ReadAll()
     
    DllCall( "FreeConsole" )
    Process, Close, %pid%
    return result
}

v2

RunWaitOne(command) {
    DetectHiddenWindows(1)
    Run(A_ComSpec,, "Hide", &pid)
    WinWait("ahk_pid" pid)
    DllCall("AttachConsole", "UInt", pid)
  
    shell := ComObject("WScript.Shell")
    exec := shell.Exec(A_ComSpec " /C " command)
    result := exec.StdOut.ReadAll()
   
    DllCall("FreeConsole")
    ProcessClose(pid)
    return result
} 

1 Comment

Outside the temp file method, this thing actually works. Be advised, if other parts of your AHK script write to StdOut and require an open console (using DllCall("AllocConsole") for instance), those parts might not work well with this.
3

If you don't need a live output, you could use the cmd box itself to save a text file of itself and then you could have autohotkey detect when the console's PID finished (using the returned result of runwait and read the outputted file into memory.

So you could do this in your run command (just a regular cmd parameter):

ipconfig > myoutput.txt
exit

Now you have a text file with the ipconfig output in it.

OR you could do the same thing, but instead of outputting to a text file, you could output to the clipboard, like this:

ipconfig | clip

Then, since the output is on the clipboard, you can easily grab it into autohotkey.

5 Comments

I see a security problem here, too. Since I'm handling sensitive data, I don't want my passwords to show up in a file or in the clipboard. Also, I need the ability to capture a stream in real time. I think using these methods, it would be rather complicated.
Well, of course, you would have to deal with that in the script. If it is sensitive, then you can't use the clipboard because something else may be tracking it. However, if you output to a text file, and load the file into memory, then you can erase the text file and delete it. I do it in some of my own applications and it works like a charm. It only takes three or four lines of autothotkey code to do it.
Files can also be accessed by any other process. Of course, the file doesn't exist very long and maybe I'm a bit paranoid, but there's still a chance that I don't want to take. Especially since AHK only deletes the files, which doesn't make the data unreadable.
Hmmm... if you erase the contents before you delete it (using fileopen).
This is brilliant! I'm using this command to save my WAN IP address into a variable: nslookup myip.opendns.com resolver1.opendns.com | find /i "address" | find /v "208.67.222.222" | clip
1

How about this script, StdoutToVar ?
It has support for 64bit consoles. http://www.autohotkey.com/board/topic/15455-stdouttovar/page-7

1 Comment

1

This has been bugging me for some time now - and finally this works !

The only prerequisite for this is MS sqlcmd.exe, a database called AHK_Dev and of course AHK_DBA to read the value when you wish to make use of it.

PS. make sure you replace {yourDir} and {yourServer} with you own values!

USE AHK_DEV
CREATE TABLE [dbo].[AHK_DOS]([dos_out] [varchar](max) NULL) ON [PRIMARY];
insert into ahk_dos select 'empty'

Create the follow script ... call it dos_out.bat

@echo off
if "%1" == "" ( 
    set v_cmd=""
) else (
    set v_cmd=%1
)
set v_cmd=%v_cmd:~1,-1%
SETLOCAL ENABLEDELAYEDEXPANSION
if "!v_cmd!" == "" (
    set v_cmd="echo ... %COMPUTERNAME% %USERNAME% %DATE% %TIME%"
    set v_cmd=!v_cmd:~1,-1!
)
set v_data=""
FOR /F "usebackq delims=¬" %%i in (`!v_cmd!`) do (
    set v_data="!v_data:~1,-1!%%i~"
)
set q_cmd="set nocount on;update ahk_dos set dos_out=N'!v_data:~1,-1!'"
"{yourDir}\Microsoft SQL Server\90\Tools\Binn\sqlcmd.exe" -S {yourServer} -E -d ahk_dev -Q !q_cmd! -W 
set q_cmd="set nocount on;select len(dos_out) as out_len, dos_out from ahk_dos"
"{yourDir}\Microsoft SQL Server\90\Tools\Binn\sqlcmd.exe" -S {yourServer} -E -d ahk_dev -Q !q_cmd! -W -w 8000
pause

you can run it from AHK using...

dosCmd2db(c) {
runwait, {yourDir\}dos_out.bat "%c%", , , dospid
msgbox %dospid% closed
}
dosCmd2db("")
dosCmd2db("echo This is a test")
dosCmd2db("dir")

As the same field is being updated each time, you would clearly need to do something between each one to make this example useful!

Try it, and let me know how you get on

Regards, Geoff

1 Comment

Ugh! To my understanding, this will create and write/read from a local database? That sounds awfully extensive. Also, one of my requirements is that no data is accessible to other processes, since I want to pass passwords as parameters. I don't want them to show up in a database.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.