News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Executing a console command

Started by MANT, December 22, 2004, 03:15:41 AM

Previous topic - Next topic

MANT

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

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MANT

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.

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

donkey

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.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

MichaelW

#5
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.

eschew obfuscation

QvasiModo

You could link your program to the system() function in the standard C library.

Mark_Larson


very nice stuff Michael :)  I think he was trying to keep a new window from popping up when executing the DOS commands.
BIOS programmers do it fastest, hehe.  ;)

My Optimization webpage
htttp://www.website.masmforum.com/mark/index.htm

MANT

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!

MANT

#9
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

MichaelW

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)
eschew obfuscation

MANT

#11
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


QvasiModo

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. :)