Hi all:
I'm trying to write a small Win32 console program that executes a command and outputs the result to the console, NOT to a 2nd console window. ShellExecute() doesn't do the trick. Then I tried CreateProcess(), which works better, but it only works on programs that are executable files. I'd like my program to execute a "dir" command at some point. Has anyone done this before? What's the trick to it?
Thanks for any advice!
Your buddy,
MANT
I have played with this idea from time to time but the last one I wrote was in PowerBASIC. Most of this stuff can be done using normal API functions and directory creation is a simple one from your current dir. I wonder what the problem is with ShellExecute() as it usually runs anything that has a file extension set up in the main shell. CreateProcess() and the old WinExec only run executable code so they are not as much use to you.
Hi Hutch:
I'm actually trying to do two things: (1) write a program that runs a console executable that will output to the current console window; and (2) have this same program also run some built-in DOS commands like "dir". For instance, if I were writing a batch interpreter, I'd need to process any commands the user entered in their batch script, whether they be "dir" or "cd" or "copy" or "attrib". I pretty much got #1 to work OK using CreateProcess(), but it doesn't do anything at all when I try to run a "dir" command. BTW, the problem with ShellExecute() was that it popped up a new console window, threw the output there, then closed the window -- all in a matter of about half a second. And it also did absolutely nothing when given the "dir" command.
Ok, I get the idea. What I have done in the past with 9x OS versions is write a batch file to disk on the fly them call it with WinExec(), wait till the task is finished then delete the batch file. With CMD.EXE you are more at the mercy of asynchronous operations so you need to use CreateProcess() with a couple of options for it to wait until the task is finished before handing control back to the calling app. I have a few variations in the MASM32 library under "shell" functions.
Now this means you must have a parser that can differentiate between internal commands and commands that are passed out to the command interpreter from the OS. Its not all that hard a job to test your set of words and pass anything else to the OS.
Hi MANT,
I wonder if you could do that with AttachConsole, if you have to process ID of the app that created the console, or even just the name, you could attach your application to that console then use it as your own. A console is capable of having multiple parents as I recall.
I'm not sure I understand the problem, and I got carried away playing with the macros, but...
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
.486 ; create 32 bit code
.model flat, stdcall ; 32 bit memory model
option casemap :none ; case sensitive
include \masm32\include\windows.inc
include \masm32\include\masm32.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
include \masm32\macros\macros.asm
newcmd MACRO options
LOCAL label
.data
label db "cmd /c "
.code
__newcmd__ = 1
$cmd_label equ <label>
ENDM
addcmd MACRO cmd
.data
IF __newcmd__ EQ 1
db cmd," "
__newcmd__ = 0
ELSE
db "&& ",cmd," "
ENDIF
.code
ENDM
execmd MACRO
.data
db 0
.code
invoke WinExec, ADDR $cmd_label, SW_SHOWNORMAL
ENDM
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
.data
.code
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
newcmd
addcmd "cmd /? > cmd.txt"
execmd
newcmd
addcmd "color 0b"
addcmd "echo"
addcmd "pause"
addcmd "cls"
addcmd "dir"
addcmd "pause"
addcmd "copy cmd.txt junk.txt"
addcmd "pause"
addcmd "echo."
addcmd "attrib junk.txt"
addcmd "echo."
addcmd "if exist junk.txt echo junk.txt exists"
addcmd "echo."
addcmd "if exist junk.txt del junk.txt"
addcmd "pause"
addcmd "echo."
addcmd "echo fini"
addcmd "echo."
; This appears to cause the "start edit cmd.txt"
; command to execute four times.
;addcmd "for %i in (a b c d) do echo i%"
;addcmd "pause"
; This will cause CMD to terminate.
;addcmd "goto bypass"
;addcmd "echo goto bypass failed"
;addcmd ":bypass"
; Either of these will prevent the console from
; closing nomally.
;addcmd "edit cmd.txt"
;addcmd "call edit cmd.txt"
; This will run the editor in a separate console
; and the current console will close normally.
addcmd "start edit cmd.txt"
; This will cause CMD to terminate.
;addcmd "if errorlevel 1 echo errorlevel 1"
; And this will not.
addcmd "if errorlevel 0 echo errorlevel 0"
addcmd "pause"
; This goto will work OK.
addcmd "echo goto EOF next"
addcmd "pause"
addcmd "goto EOF"
addcmd "echo goto EOF failed"
addcmd "pause"
execmd
; And this appears to execute while the previous
; command sequence is waiting for user input.
;newcmd
; addcmd "edit cmd.txt"
;execmd
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
Updated the code after doing some more testing. I could find no way to use labels in the command sequence, other than the predefined EOF label. I seem to recall someone doing something similar on the old forum.
You could link your program to the system() function in the standard C library.
very nice stuff Michael :) I think he was trying to keep a new window from popping up when executing the DOS commands.
Thanks for all the input, everyone, it's much appreciated!
Michael has the right solution, at least for me. If I precede any command with "cmd /c" it works like a charm (the commands work, and the output goes right into my main console window. Wow!!!! :toothy :eek :bg :thumbu :thumbu :thumbu
Thanks, Michael!
For your amusement, here's the code for my test program...
.686 ; minimum processor needed for 32 bit
.model flat, stdcall ; FLAT memory model & STDCALL calling
option casemap :none ; set code to case sensitive
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\advapi32.lib
;------------------------------------------------------------------------------
.data
_cmd_c db "cmd /c %s", 0
_echo db "cmd /c echo ", 80 dup("_"), 0
_tracert db "tracert www.google.com", 0
_dir db "dir", 0
_attrib db "attrib", 0
startupinfo STARTUPINFO { SIZEOF STARTUPINFO }
processinfo PROCESS_INFORMATION { SIZEOF PROCESS_INFORMATION }
.code
;------------------------------------------------------------------------------
winexec proc, s:dword
xor ecx, ecx
invoke CreateProcess, ecx, s, ecx, ecx, ecx, \
NORMAL_PRIORITY_CLASS, ecx, ecx, \
addr startupinfo, addr processinfo
ret
winexec endp
;------------------------------------------------------------------------------
doscmd proc uses ebx, s:dword
local buf[1024]:byte, exitcode:dword
; add "cmd /c" to command string
invoke wsprintf, addr buf, addr _cmd_c, s
; execute the command
invoke winexec, addr buf
; wait for command to finish - polling - HACK! HACK!
POLL: invoke Sleep, 100
invoke GetExitCodeProcess, processinfo.hProcess, addr exitcode
test eax, eax
jz @F
cmp exitcode, STILL_ACTIVE
je POLL
@@:
; once command is finished, draw a line
invoke winexec, addr _echo
invoke Sleep, 50
ret
doscmd endp
;------------------------------------------------------------------------------
start: invoke doscmd, addr _attrib
invoke doscmd, addr _tracert
invoke doscmd, addr _dir
xor eax, eax
invoke ExitProcess, eax
ret
;------------------------------------------------------------------------------
end start
Cool!
I have one question, though. It appears to work the same after the entire polling loop is replaced with:
invoke WaitForSingleObject, processinfo.hProcess, INFINITE
I recall there being a previous discussion of this, but I don't remember the details. Is there some non-obvious reason for using the polling loop?
(The tt tag is perfect for single line code examples, another advantage of SMF over phpBB)
Quote from: MichaelW on December 22, 2004, 07:14:07 PM
Is there some non-obvious reason for using the polling loop?
No reason other than my own ignorance. The last time I worked with processes and thread was years ago. I looked for a wait function on Google but must not have been using the right keywords. Thanks so much for the tip, that really improves the code, and it works perfectly!
.686 ; minimum processor needed for 32 bit
.model flat, stdcall ; FLAT memory model & STDCALL calling
option casemap :none ; set code to case sensitive
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\advapi32.lib
;------------------------------------------------------------------------------
.data
_cmd_c db "cmd /c %s", 0
_echo db "cmd /c echo ", 80 dup("_"), 0
_tracert db "tracert www.google.com", 0
_dir db "dir", 0
_attrib db "attrib", 0
startupinfo STARTUPINFO { SIZEOF STARTUPINFO }
processinfo PROCESS_INFORMATION { SIZEOF PROCESS_INFORMATION }
.code
;------------------------------------------------------------------------------
winexec proc, s:dword
xor ecx, ecx
invoke CreateProcess, ecx, s, ecx, ecx, ecx, \
NORMAL_PRIORITY_CLASS, ecx, ecx, \
addr startupinfo, addr processinfo
invoke WaitForSingleObject, processinfo.hProcess, INFINITE
ret
winexec endp
;------------------------------------------------------------------------------
doscmd proc uses ebx, s:dword
local buf[1024]:byte
invoke wsprintf, addr buf, addr _cmd_c, s ; add "cmd /c " to cmd
invoke winexec, addr buf ; execute command
invoke winexec, addr _echo ; draw line
ret
doscmd endp
;------------------------------------------------------------------------------
start: invoke doscmd, addr _attrib
invoke doscmd, addr _tracert
invoke doscmd, addr _dir
xor eax, eax
invoke ExitProcess, eax
ret
;------------------------------------------------------------------------------
end start
Quote from: MANT on December 22, 2004, 05:24:25 PM
Thanks for all the input, everyone, it's much appreciated!
Michael has the right solution, at least for me. If I precede any command with "cmd /c" it works like a charm (the commands work, and the output goes right into my main console window. Wow!!!! :toothy :eek :bg :thumbu :thumbu :thumbu
Thanks, Michael!
You're right, in fact I just disassembled a little test program in C, and it turns out the system() procedure does that too. Just make sure to get the Windows version first, and run "cmd /c" for Win_NT/2K/XP, or "command /c" for Win95/98/ME. :)