Page 1 of 1

Keeping objShell open to Exec several tasks

Posted: 11 Jul 2014, 08:36
by toralf
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.

Re: Keeping objShell open to Exec several tasks

Posted: 11 Jul 2014, 09:05
by Coco
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

Re: Keeping objShell open to Exec several tasks

Posted: 11 Jul 2014, 11:11
by toralf
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.

Re: Keeping objShell open to Exec several tasks

Posted: 11 Jul 2014, 11:21
by Coco
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
}

Re: Keeping objShell open to Exec several tasks

Posted: 11 Jul 2014, 12:53
by toralf
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.

Re: Keeping objShell open to Exec several tasks

Posted: 11 Jul 2014, 13:07
by toralf
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

Re: Keeping objShell open to Exec several tasks

Posted: 11 Jul 2014, 13:11
by Coco
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
}

Re: Keeping objShell open to Exec several tasks

Posted: 30 Aug 2014, 06:53
by Dougal
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

Re: Keeping objShell open to Exec several tasks

Posted: 30 Aug 2014, 20:00
by lexikos
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"
    }
}

Re: Keeping objShell open to Exec several tasks

Posted: 30 Aug 2014, 22:11
by Dougal
@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.

Re: Keeping objShell open to Exec several tasks

Posted: 30 Aug 2014, 22:56
by joedf
ReadConsoleOutput() ?