Keeping objShell open to Exec several tasks

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
toralf
Posts: 868
Joined: 27 Apr 2014, 21:08
Location: Germany

Keeping objShell open to Exec several tasks

11 Jul 2014, 08:36

I have process several files to read their EXIF data. Currently I use a code similar to one that garry had posted:

Code: Select all

exiftool = Path to Exiftool
Loop, C:\*
{
    objShell := ComObjCreate("WScript.Shell")
    objExec := objShell.Exec(ComSpec " /c exiftool " A_LoopFileLongPath)
    while, !objExec.StdOut.AtEndOfStream
       Exif[A_Index] .= objExec.StdOut.ReadAll()
}
It works so far, but basically for each file a cmd window pops up, disapears and the cmd window for the next files comes up, and so on.

Is there a way to create one cmd window, do all the collection/execution/reading and then close the window? Thus, one window is visible all the time, but at least it is not constantly flickering.
ciao
toralf
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
Contact:

Re: Keeping objShell open to Exec several tasks

11 Jul 2014, 09:05

You can put something like this at the beginning, this won't show any console:

Code: Select all

DetectHiddenWindows On
Run %ComSpec% /k,, Hide UseErrorLevel, pid
if ErrorLevel
	return false
while !WinExist("ahk_pid " pid)
	Sleep 10
DllCall("AttachConsole", "UInt", pid)
Or you can use AllocConsole, and then hide(optionally, if you don't want/need it) the console
toralf
Posts: 868
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: Keeping objShell open to Exec several tasks

11 Jul 2014, 11:11

The reason I use objShell is that I can retrieve the StrOut without the need to write an intermediate file.
I assume with your proposal I would still have to write intermediate files to read the StrOut into a Var, correct?
If this is the case, then it isn't really what I'm looking for.

I would like to execute a command line tool several times in a loop and read its output on StrOut into a var each time without using intermediate files.

I have a solution but for each iteration a new window pops up and disappears. It would be great if this could be changed to only have a single window poping up for the whole loop duration.
ciao
toralf
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
Contact:

Re: Keeping objShell open to Exec several tasks

11 Jul 2014, 11:21

My solution attaches the hidden console, hence, no window pop outs, even with repeated calls.
Here's a function that demonstrates the solution (my apologies as the code is for v2and I'm being lazy right now to convert it to v1.1):

Code: Select all

MsgBox % GetStdOut('ping www.google.com')

GetStdOut(cmd, init:=false) {
	static on_exit := new (base:={__New:'GetStdOut', __Delete:'GetStdOut'})(true)
	; static hCon

	if IsObject(cmd) {
		if init {
			dhw := A_DetectHiddenWindows
			A_DetectHiddenWindows := true
			Run('%A_ComSpec% /k',, 'Hide UseErrorLevel', pid)
			if (ErrorLevel == 'ERROR'), return false
			while !WinExist('ahk_pid %pid%')
				Sleep(10)
			DllCall('AttachConsole', 'UInt', cmd.PID:=pid)
			/*
			hCon := DllCall(
			(Q
				'CreateFile',
				'Str', 'CONOUT$',
				'UInt', 0xC0000000,
				'UInt', 7,
				'UInt', 0,
				'UInt', 3,
				'UInt', 0,
				'UInt', 0
			))
			*/
			A_DetectHiddenWindows := dhw
			; FileAppend('INITIALIZED`n', '*') ;// for debugging only
		} else {
			; DllCall('CloseHandle', 'Ptr', hCon)
			DllCall('FreeConsole')
			ProcessClose(cmd.PID)
			FileAppend('DELETE`n', '*')
		}
		return init ? cmd : true
	}
	shell := ComObjCreate("WScript.Shell")
	exec := shell.Exec(cmd)
	while !exec.StdOut.AtEndOfStream
		stdout := exec.StdOut.ReadAll()
	return stdout
}
toralf
Posts: 868
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: Keeping objShell open to Exec several tasks

11 Jul 2014, 12:53

Thanks for the code.
I tried to translate it to 1.1

Code: Select all

MsgBox % GetStdOut("ping www.google.com")
MsgBox % GetStdOut("ping www.yahoo.com")
MsgBox % GetStdOut("ping www.ahkscript.org")

GetStdOut(cmd, init:=false) {
  static on_exit := new (base:={__New:"GetStdOut", __Delete:"GetStdOut"})(true)
  If IsObject(cmd) {
    If init {
      dhw := A_DetectHiddenWindows
      DetectHiddenWindows, on
      Run, %ComSpec% /k ,, Hide UseErrorLevel, pid
      If (ErrorLevel = "ERROR")
        return false
      While !WinExist("ahk_pid " pid)
        Sleep 10
      DllCall("AttachConsole", "UInt", cmd.PID:=pid)
      DetectHiddenWindows, %dhw%
    }Else{
      DllCall("FreeConsole")
      Process, Close, % cmd.PID
    }
    Return init ? cmd : true
  }
  Shell := ComObjCreate("WScript.Shell")
  Exec := Shell.Exec(cmd)
  while !Exec.StdOut.AtEndOfStream
    Stdout := Exec.StdOut.ReadAll()
  return Stdout
}
It works in general. But three windows pop up.
I also do not see that cmd is ever an object. So the first IF is never true.
But maybe I did something wrong when converting to 1.1.
ciao
toralf
toralf
Posts: 868
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: Keeping objShell open to Exec several tasks

11 Jul 2014, 13:07

I played around and this seems to work now in 1.1

Code: Select all

MsgBox % GetStdOut("ping www.google.com")
MsgBox % GetStdOut("ping www.yahoo.com")
MsgBox % GetStdOut("ping www.ahkscript.org")
GetStdOut()

GetStdOut(cmd := ""){   ;http://ahkscript.org/boards/viewtopic.php?t=3941
  static PID
  If !PID {
    dhw := A_DetectHiddenWindows
    DetectHiddenWindows, on
    Run, %ComSpec% /k ,, Hide UseErrorLevel, pid
    If (ErrorLevel = "ERROR")
      return false
    While !WinExist("ahk_pid " pid)
      Sleep 10
    DllCall("AttachConsole", "UInt", pid)
    DetectHiddenWindows, %dhw%
  }
  If cmd{
    Shell := ComObjCreate("WScript.Shell")
    Exec := Shell.Exec(cmd)
    while !Exec.StdOut.AtEndOfStream
      Stdout := Exec.StdOut.ReadAll()
    return Stdout
  }Else{
    DllCall("FreeConsole")
    Process, Close, %PID%
  }
} 
Is this what you intended? Thanks anyway

Edit: I simplified the code slightly: calling GetStdOut() without a cmd closes the console
Last edited by toralf on 11 Jul 2014, 13:19, edited 1 time in total.
ciao
toralf
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
Contact:

Re: Keeping objShell open to Exec several tasks

11 Jul 2014, 13:11

Yep, here's the fix for my code. Apparently, for v1.1, the following does not work: instance := new (base:={})(), must do base := {}, instance := new base():

Code: Select all

MsgBox % GetStdOut("ping www.google.com")

GetStdOut(cmd, init:=false) {
	static base := {__New:"GetStdOut", __Delete:"GetStdOut"}
	static on_exit := new base(true)

	if IsObject(cmd) {
		if init {
			dhw := A_DetectHiddenWindows
			DetectHiddenWindows On
			Run %ComSpec% /k,, Hide UseErrorLevel, pid
			if (ErrorLevel == "ERROR")
				return false
			while !WinExist("ahk_pid " pid)
				Sleep 10
			DllCall("AttachConsole", "UInt", cmd.PID:=pid)
			DetectHiddenWindows %dhw%
		} else {
			DllCall("FreeConsole")
			Process Close, % cmd.PID
		}
		return init ? cmd : true
	}
	shell := ComObjCreate("WScript.Shell")
	exec := shell.Exec(cmd)
	while !exec.StdOut.AtEndOfStream
		stdout := exec.StdOut.ReadAll()
	return stdout
}
Dougal
Posts: 26
Joined: 19 Aug 2014, 16:51

Re: Keeping objShell open to Exec several tasks

30 Aug 2014, 06:53

You can concatenate commands in cmd.exe /c, which will work if you don't have too many commands to run.

Code: Select all

exiftool = Path to Exiftool
Loop, C:\*
    strCmds .= (strCmds ? " & " : "") """" exiftool """ """ A_LoopFileLongPath """"
objShell := ComObjCreate("WScript.Shell")
objExec := objShell.Exec(ComSpec " /c " strCmds)
while, !objExec.StdOut.AtEndOfStream
Exif[A_Index] .= objExec.StdOut.ReadAll()
}
Your StdOut stream will include the output from all the commands, not individually.
Using & unconditionally runs each command, && only runs if previous command succeeds, || only runs if previous command fails and you can group sets of commands with ( ).
So you could group and join your exif commands with && to stop as soon as an error occurs, and use || to append a command to throw an error eg exit /B 1.

Code: Select all

(exiftool path1 && exiftool path2 && exiftool path 3) || exit /B 1
Maximum command line length is 8191, so you might want to add a check in the file loop to stop before exceeding that length, run the commands and then process the next batch of files.

Cheers
lexikos
Posts: 9635
Joined: 30 Sep 2013, 04:07
Contact:

Re: Keeping objShell open to Exec several tasks

30 Aug 2014, 20:00

I propose the use of stdin...

Code: Select all

shell := ComObjCreate("WScript.Shell")
exec := shell.Exec(ComSpec " /Q /k echo off")
commands =
(
echo doing some stuff.
echo doing some more stuff...
echo all done!
exit
)
exec.StdIn.WriteLine(commands)
MsgBox % exec.StdOut.ReadAll()
It's also possible to respond to each command, though a bit trickier. Here we have our own interactive command prompt:

Code: Select all

exec := ComObjCreate("WScript.Shell").Exec(ComSpec " /Q")
exec.StdIn.WriteLine("prompt $_" (prompt := ":AWAITING YOUR COMMAND:") "$_")
while exec.Status = 0 && !(prompt == exec.StdOut.ReadLine())
    continue
while exec.Status = 0 {
    InputBox cmd,, %output%
    exec.StdIn.WriteLine(ErrorLevel ? "exit" : cmd), output := ""
    Loop {
        line := exec.StdOut.ReadLine()
        if (line == prompt || exec.Status != 0)
            break
        output .= line "`n"
    }
}
Dougal
Posts: 26
Joined: 19 Aug 2014, 16:51

Re: Keeping objShell open to Exec several tasks

30 Aug 2014, 22:11

@OP
Why are you using Wscript.Shell exec with comspec instead of exiftool directly? I assume it is a console app since you are retrieving StdOut. You could use .exec("exiftool " path) instead of .exec(comspec " /c exiftool " path). AFAIK, the only reason to use comspec is either access to built in commands or multiple commands.

Not sure if it suits your purpose, but NirSoft ExifDataView v1.02 (http://www.nirsoft.net/utils/exif_data_view.html) can save the exif data to a text file using command option, so a file loop with one line runwait command, then fileread would give you the same data with no command windows or mucking around with StdOut.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: arrondark, DRS, Rohwedder and 150 guests