RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by SKAN » 21 Nov 2020, 09:41

burque505 wrote:
20 Nov 2020, 09:03
@william_ahk, try this code. There's apparently a carriage return in the stdout from 'echo'.

Code: Select all

#Include RunCmd.ahk

str1 := RunCMD(A_ComSpec . " /c echo SKAN")
str1 := RegExReplace(str1, "`r`n")
msgbox % str1
If (str1 == "SKAN") 
	msgbox Equal
else
	msgbox %str1% Is Not Equal to SKAN
Regards, burque505


Thanks @burque505

We may also use RTrim()

:thumbup:

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

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by burque505 » 22 Nov 2020, 10:51

@SKAN, even better! :thumbup:
Regards,
burque505

flubber42
Posts: 8
Joined: 03 Nov 2019, 09:58

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by flubber42 » 01 Apr 2021, 12:55

Hi. Thanks for this great script SKAN. I'm using it successfully in a project which makes use of the Spotify Web API and curl to make server calls to retrieve track information (artist, album, duration, progress etc) and display these in a permanent GUI on a second monitor. These details come back in the form of a JSON response which I then store to my variable 'Output' and parse.

Here is the relevant portion of the code;

Code: Select all

c="https api.spotify.com /v1/me/player"  Broken Link for safety
d="Accept: application/json"
e="Content-Type: application/json"
f:="Authorization: Bearer " . token2 ""
g="%f%"
mX:="curl -s -X GET " . c " -H " . d " -H " . e " -H " . g ""

SetTimer, UpdateTime, 200	; this frequency is necessary to implement track timer functionality

UpdateTime:
Output:=RunCMD(mX)
--- bunch of parsing code ---
return
The only problem with this implementation is that it causes a continual spike in the 'Antimalware Service Executable'. On my modestly specced machine it averages around 5%. I appreciate that figure is not ridiculously high but i thought it worth asking if anyone had any suggestions for improving efficiency or perhaps could suggest a completely different approach. I have tried adding the relevant programs as exclusions in Windows Security to no avail. I also have a version of my project which hands off this portion of code to Python which doesn't upset the antivrus at all but does seem like an ugly workaround.

PS. The only modification i've made to RunCMD v0.94 is changing the Sleep integer to "1"

Tre4shunter
Posts: 139
Joined: 26 Jan 2016, 16:05

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by Tre4shunter » 01 Apr 2021, 14:07

Hey @flubber42

What about using an implementation of MSXML2 directly? HEres my [gross] implementation of it, which i use for all sorts of API calls with AHK, rather using curl through a cmd prompt.

Feel free to clean up my gross code below, lol

Code: Select all

sendstring(type, url, body="", Headers="", URLParams="") {
	pwhr := comobjcreate("MSXML2.ServerXMLHTTP")
	
	for k,v in URLParams
		Params .= k "=" v "&"
	
	url := (isobject(URLParams)) ? url "?" substr(params,1,-1)  : url
	pwhr.open(type, url, false)
	
	for k,v in Headers
		pwhr.setrequestheader(k,v)

	if isobject(body)
	{
		for k,v in body
		{
			for k1,v1 in v
				body .= k1 "=" v1 "&"
		}
		body:=substr(body,1,-1)
	}
	
	pwhr.Send(body)
	Response := pwhr.Responsetext
	
	return, Response
}
Thanks,

Tre4

flubber42
Posts: 8
Joined: 03 Nov 2019, 09:58

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by flubber42 » 01 Apr 2021, 15:10

Cheers Tre4, that's looks like it has potential. Not sure how to adapt it to my needs just yet.. but hopefully i'll get there eventually!

Tre4shunter
Posts: 139
Joined: 26 Jan 2016, 16:05

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by Tre4shunter » 01 Apr 2021, 15:20

Something like this should get you going:

Code: Select all

token2 := "Your_Token_Here"
url := "https api.spotify.com/v1/me/player"
headers := {"Accept":"application/json","Content-Type":"application/json","Authorization":"Bearer "  token2}

result := sendstring("GET",url,"",headers)
msgbox, % result
return

sendstring(type, url, body="", Headers="", URLParams="") {
	pwhr := comobjcreate("MSXML2.ServerXMLHTTP")
	
	for k,v in URLParams
		Params .= k "=" v "&"
	
	url := (isobject(URLParams)) ? url "?" substr(params,1,-1)  : url
	pwhr.open(type, url, false)
	
	for k,v in Headers
		pwhr.setrequestheader(k,v)

	if isobject(body)
	{
		for k,v in body
		{
			for k1,v1 in v
				body .= k1 "=" v1 "&"
		}
		body:=substr(body,1,-1)
	}
	
	pwhr.Send(body)
	Response := pwhr.Responsetext
	
	return, Response
}
I'm only wondering if it will be an issue that it will definitely be 'blocking' as its not asynchronous. Is your implementation of RunCMD?

-Tre4

flubber42
Posts: 8
Joined: 03 Nov 2019, 09:58

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by flubber42 » 01 Apr 2021, 17:03

Thanks for expanding on your earlier example. Not had a chance to test it yet but no doubt it will save me a lot of time. I am using the 'non-blocking' version of RunCMD but until I properly implement your suggestion I can't say for sure if this is a working solution. I shall report back! Your help is greatly appreciated.

User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by SKAN » 03 Apr 2021, 00:33

flubber42 wrote:
01 Apr 2021, 12:55
PS. The only modification i've made to RunCMD v0.94 is changing the Sleep integer to "1"
I have also been wondering about that sleep. Using a DllCall for sleep is bad. I will test it with changes.
Please allow me some time. Thanks.

alesyt0h
Posts: 214
Joined: 28 Jan 2015, 20:37

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by alesyt0h » 11 Apr 2021, 04:08

SKAN wrote:
14 Apr 2020, 13:00
RunCMD() is a rewrite and renamed version of my old StdOutToVar() which was adapted from Sean's StdoutToVar

RunCMD( CmdLine, WorkingDir, Codepage, Fn )
RunCMD() runs a console utility in windowless mode and captures its output one line at a time, concatenates them and returns the complete text.
The lines that are read maybe redirected to a helper function for pre-process. Within the helper functions individual lines they maybe omitted or reformatted.
Unlike previous versions, the current RunCMD() v0.93 reads from a non-blocking pipe enabled via PIPE_NOWAIT flag passed to SetNamedPipeHandleState()
During the lifetime of the created process, a global array element A_Args.RunCMD.PID will contain the PID and assigning a 0 (false) to this element will cancel RunCMD().
The created process will inherit the Priority of the script. Therefore Process, Priority,, High maybe used in scripts to see some marginal improvement.
 
Parameters:
  • CmdLine : The command line to be executed.
  • WorkingDir : Use this parameter to specify Working Directory. If omitted the launched process will default to A_WorkingDir.
  • Codepage : Default is Cp0. Use UTF-8 or UTF-16 when dealing with unicode.
  • Fn : Helper function name. When a helper function is available, RunCMD() will call it with two parameters Line, LineNum.
    You may control the output within the helper function.
 
  
Examples
 
Powershell examples: https://www.autohotkey.com/boards/viewtopic.php?p=341237#p341237
 
 
The function:

Code: Select all

RunCMD(CmdLine, WorkingDir:="", Codepage:="CP0", Fn:="RunCMD_Output") {  ;         RunCMD v0.94        
Local         ; RunCMD v0.94 by SKAN on D34E/D37C @ autohotkey.com/boards/viewtopic.php?t=74647                                                             
Global A_Args ; Based on StdOutToVar.ahk by Sean @ autohotkey.com/board/topic/15455-stdouttovar

  Fn := IsFunc(Fn) ? Func(Fn) : 0
, DllCall("CreatePipe", "PtrP",hPipeR:=0, "PtrP",hPipeW:=0, "Ptr",0, "Int",0)
, DllCall("SetHandleInformation", "Ptr",hPipeW, "Int",1, "Int",1)
, DllCall("SetNamedPipeHandleState","Ptr",hPipeR, "UIntP",PIPE_NOWAIT:=1, "Ptr",0, "Ptr",0)

, P8 := (A_PtrSize=8)
, VarSetCapacity(SI, P8 ? 104 : 68, 0)                          ; STARTUPINFO structure      
, NumPut(P8 ? 104 : 68, SI)                                     ; size of STARTUPINFO
, NumPut(STARTF_USESTDHANDLES:=0x100, SI, P8 ? 60 : 44,"UInt")  ; dwFlags
, NumPut(hPipeW, SI, P8 ? 88 : 60)                              ; hStdOutput
, NumPut(hPipeW, SI, P8 ? 96 : 64)                              ; hStdError
, VarSetCapacity(PI, P8 ? 24 : 16)                              ; PROCESS_INFORMATION structure

  If not DllCall("CreateProcess", "Ptr",0, "Str",CmdLine, "Ptr",0, "Int",0, "Int",True
                ,"Int",0x08000000 | DllCall("GetPriorityClass", "Ptr",-1, "UInt"), "Int",0
                ,"Ptr",WorkingDir ? &WorkingDir : 0, "Ptr",&SI, "Ptr",&PI)  
     Return Format("{1:}", "", ErrorLevel := -1
                   ,DllCall("CloseHandle", "Ptr",hPipeW), DllCall("CloseHandle", "Ptr",hPipeR))

  DllCall("CloseHandle", "Ptr",hPipeW)
, A_Args.RunCMD := { "PID": NumGet(PI, P8? 16 : 8, "UInt") }      
, File := FileOpen(hPipeR, "h", Codepage)

, LineNum := 1,  sOutput := ""
  While (A_Args.RunCMD.PID + DllCall("Sleep", "Int",0))
    and DllCall("PeekNamedPipe", "Ptr",hPipeR, "Ptr",0, "Int",0, "Ptr",0, "Ptr",0, "Ptr",0)
        While A_Args.RunCMD.PID and (Line := File.ReadLine())
          sOutput .= Fn ? Fn.Call(Line, LineNum++) : Line

  A_Args.RunCMD.PID := 0
, hProcess := NumGet(PI, 0)
, hThread  := NumGet(PI, A_PtrSize)

, DllCall("GetExitCodeProcess", "Ptr",hProcess, "PtrP",ExitCode:=0)
, DllCall("CloseHandle", "Ptr",hProcess)
, DllCall("CloseHandle", "Ptr",hThread)
, DllCall("CloseHandle", "Ptr",hPipeR)

, ErrorLevel := ExitCode

Return sOutput  
}
Hi Skan!

First, thank you so much for this, i've been using this for a while with perfection!

Now I have a little trouble, for debugging purposes, I will love to "quick" edit the code for show the console or hide it as default.

How can I achive this?
Thanks again SKAN and regards

User avatar
Thoughtfu1Tux
Posts: 125
Joined: 31 May 2018, 23:26

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by Thoughtfu1Tux » 30 May 2021, 00:08

Loving this as a replacement to StdOutToVar! Thank you very much.
I'm having an issue with passing a filepath with a space to powershell:

The command i'm running is:

Code: Select all

Command = powershell (Get-Item "C:\Program Files\Chromium\Application\chrome.exe").VersionInfo | Select-Object ProductVersion
Msgbox % "RunCMD(Command): " RunCMD(Command)
But i get the following error as a return:

Code: Select all

Get-Item : A positional parameter cannot be found that accepts argument 'Files\Chromium\Application\chrome.exe'.
At line:1 char:2
+ (Get-Item C:\Program Files\Chromium\Application\chrome.exe).VersionIn ...
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-Item], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetItemCommand
 
I'm not sure how to go about at fixing this, anyone got any ideas?

User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

RunCMD() v0.94

Post by SKAN » 30 May 2021, 01:37

Hi @Thoughtfu1Tux

Using single quote (instead of double quote) seems to solve the problem.
The following works fine for me:

Code: Select all

Command := "powershell (Get-Item 'C:\Program Files\7-Zip\7zFM.exe').VersionInfo | Select-Object ProductVersion"
Msgbox % "RunCMD(Command): " RunCMD(Command)

user16749
Posts: 6
Joined: 15 Dec 2020, 18:40

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by user16749 » 02 Jun 2021, 12:57

Hello @SKAN, thanks for the improvement on this function. However I seem to have issues passing more than a single environment variable
lpEnvironment
to the
CreateProcess()
function. Docs say they need to be null-terminated and works fine in C++ but in AHK I cannot seem to make it work for more than a single variable.

Code: Select all

; Either

"AStr","foo=bar" Chr(0) "xyz=123"

; Or

envs := ""
str := "foo=bar" Chr(0) "xyz=123"
len := StrPut(str) * 2
VarSetCapacity(envs, len)
StrPut(str, &envs)

"AStr",envs
Both ways only set the
foo
variable with the other appended as value. Any pointers (pun intended) will be greatly appreciated.

User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by SKAN » 02 Jun 2021, 14:43

user16749 wrote:
02 Jun 2021, 12:57
Any pointers (pun intended) will be greatly appreciated.
You could init lpEnvironment as follows

Code: Select all

#NoEnv
#Warn
#SingleInstance, Force

AStrPuts(ByRef Out, In) {
Local n := VarSetCapacity(Out, StrLen(In)+2, 0) * 0
  Loop, Parse, In, |
    n += StrPut(A_LoopField, &Out+n, "cp0")
Return &Out
}

lpEnvironment := AStrPuts(Env:=0, "foo=bar|xyz=123")
 
... and pass it as "Ptr",lpEnvironment to CreateProcess()
 
Well.. I wouldn't want to sweat for it.
 

Code: Select all

EnvSet, foo, bar
EnvSet, xyz, 123
RunCmd(...)

peameedo112
Posts: 6
Joined: 12 Feb 2020, 19:14

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by peameedo112 » 03 Jun 2021, 01:57

Hi
Im tring to use this function to return some adb messages, but ahk will be no respond/crushed while the service adb.exe not exist.
Heres the code:

Code: Select all

devices := RunCMD("adb devices")
To solve this problem, I have to get another way:

Code: Select all

Process, Exist, adb.exe
if !ErrorLevel
	RunWait,  %comSpec% /c adb devices
devices := RunCMD("adb devices")
But I want to know, have another way to solve this problem?
Many thanks!

User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by SKAN » 03 Jun 2021, 03:10

peameedo112 wrote:
03 Jun 2021, 01:57
Im tring to use this function to return some adb messages
I don't know what adb.exe does. You could try

Code: Select all

RunCmd(A_ComSpec . " /c adb devices", "C:\Program Files\[path-to-exe]")

User avatar
TheArkive
Posts: 1027
Joined: 05 Aug 2016, 08:06
Location: The Construct
Contact:

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by TheArkive » 03 Jun 2021, 05:29

@peameedo112

One potential problem you face is trying to RunWait on adb.exe. ADB actually never terminates. So if the process does not exist, and is then run with RunWait, your last line will never actually execute until ADB terminates.

SKAN gave you the best solution. Of course, if ADB is run for the first time, there is an unavoidable delay while the ADB server starts up the first time. But after that the return will be basically instant.




blue83
Posts: 157
Joined: 11 Apr 2018, 06:38

Re: RunCMD() v0.94 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.

Post by blue83 » 07 Jun 2021, 16:46

Hi,

Can you help me.

This is working fine

Code: Select all

avar := RunCmd("" A_ScriptDir "\Upload.exe", "" A_ScriptDir "\" folder1 "", "UTF-8")
but this code with 2 subfolders does not

Code: Select all

avar := RunCmd("" A_ScriptDir "\Upload.exe", "" A_ScriptDir "\" folder1 "\" folder2 "", "UTF-8")

Post Reply

Return to “Scripts and Functions (v1)”