The MASM Forum Archive 2004 to 2012

General Forums => The Workshop => Topic started by: Jimg on June 04, 2008, 07:14:56 PM

Title: Getting a "DIR"
Post by: Jimg on June 04, 2008, 07:14:56 PM
I was working on a program where once again I needed a list of files to work on.

Normally I would break out the FindFirst/FindNext stuff, but for this quicky, I thought I would just execute a DIR command.

So, I did this, which works.  The question is, is there some smaller, simpler way to get a list of files to use?

include \masm32\include\masm32rt.inc
.data
  open db "open",0
  cmd  db "cmd",0
  parm db "/c dir *.htm /s/w/b/a-d > "  ; continued on next line
  dest db "C:\allhtm.txt",0
  ddir db "C:\helpfiles",0
  hanx dd 0
  hres dd 0
.code

program:

invoke ShellExecute,0,addr open,addr cmd,addr parm,addr ddir,SW_HIDE

.if eax>32
    mov hanx,eax
    .repeat
        invoke Sleep,100
        invoke GetExitCodeProcess,hanx,addr hres
    .until hres != STILL_ACTIVE


; the rest is just testing stuff
.data
  errx db "error number "
  buff db 15 dup (0)
  answ dd 0
  alen dd 0
  aline dd 170 dup (0)
  spos dd 0
.code
    invoke read_disk_file,addr dest,addr answ,addr alen
    invoke readline,answ,addr aline,spos
    mov spos,eax
    invoke MessageBox,0,addr aline,0,0
    invoke GlobalFree,answ
.else
    invoke dwtoa,eax,addr buff
    invoke MessageBox,0,addr errx,0,0
.endif
invoke ExitProcess,eax
end program
Title: Re: Getting a "DIR"
Post by: Jimg on June 04, 2008, 08:09:17 PM
Opps.  I just checked and I'm getting 0 as a return from GetExitCodeProcess.  This must not be the correct api to use to see if shellexecute is done, what is?
Title: Re: Getting a "DIR"
Post by: Jimg on June 04, 2008, 08:37:44 PM
Well, it appears that the number that ShellExecute returns is worthless unless it's an error code < 33.  So no way to tell when it's done.
The only alternatives I can think of are ShellExecuteEX and CreateProcess, both of which are much bigger and more complicated and generally yucky.  Not simpler and smaller as per my original request.  So much for quickies.
Title: Re: Getting a "DIR"
Post by: jj2007 on June 04, 2008, 09:04:24 PM
Oops - I had already scratched my head wondering how I possible could have overlooked, in all those years, such an elegant way to launch a synchronous process; without all that "yucky" stuff... if somebody gives me the name of the guy at M$ who ordered to "forget" the wait parameter in WinExec & friends, he should watch out for his b***s.

Back to serious work: Would you consider the LB_DIR Message "simple and elegant"? Another option is the DlgDirList (http://msdn.microsoft.com/en-us/library/bb761366(VS.85).aspx) function.
Title: Re: Getting a "DIR"
Post by: Jimg on June 04, 2008, 11:45:30 PM
Personally, I would have to say Nothing about a listbox is simpler or smaller than anything.  Probably my least favorite control to hassle with.

But...  WinExec is certainly smaller and simpler.  Thanks.

.data
cmd2 db "cmd /c dir C:\helpfile\*.htm /s/w/b/a-d > c:allhtm.txt",0
.code
invoke WinExec,addr cmd2,0


Then read output and try again if you get a read error ??
Title: Re: Getting a "DIR"
Post by: jj2007 on June 05, 2008, 08:46:12 AM
WinExec was an almost perfect function. "Almost" because it still had that completely useless second para that nobody ever used. Together with GetModuleUsage, you would have been able to do what you want. Then, M$ decided that it was not a good idea if amateur programmers managed to do these things; so they "deprecated" WinExec and abolished GetModuleUsage. So now we are stuck with that rubbish sequence of CreateProcessWithTenNullParams+WaitForBloodyObject...

The correct procedure is mentioned here on M$ support (http://support.microsoft.com/kb/129796/EN-US/).

How To Use a 32-Bit Application to Determine When a Shelled Process Ends

The Win32 API has integrated functionality that enables an application to wait until a shelled process has completed. To use these functions, you need a handle to the shelled process. The easiest way to achieve this is to use the CreateProcess() API function to launch your shelled program rather than Visual Basic's Shell() function.

Creating the Shelled Process
In a 32-bit application, you need to create an addressable process. To do this, use the CreateProcess() function to start your shelled application. The CreateProcess() function gives your program the process handle of the shelled process through one of its passed parameters.

Waiting for the Shelled Process to Terminate
Having used CreateProcess() to get a process handle, pass that handle to the WaitForSingleObject() function. This causes your Visual Basic application to suspend execution until the shelled process terminates.

Getting the Exit Code from the Shelled Application
It was common for a DOS application to return an exit code indicating the status of the completed application. While Windows provides other ways to convey the same information, some applications only provide exit codes. Passing the process handle to the GetExitCodeProcess() API allows you to retrieve this information.
Title: Re: Getting a "DIR"
Post by: sinsi on June 05, 2008, 09:24:17 AM
If you are going to the trouble of ShellExecute/WinExec then CreateProcess can't be any harder can it?
Besides, it gives you access to a handle that you can use with WaitForSingleObject, and know when it's finished.

Quote from: jj2007 on June 05, 2008, 08:46:12 AMCreateProcessWithTenNullParams
As bad as the 12 params (48 bytes) passed on the stack to CreateWindowEx - 8 params which can be 0/NULL/zero (ffs)




BTW, can we leave off the "M$" thing please? Too bad Apple (or Mac or Linux) doesn't have an "s" in its name... ::)
Title: Re: Getting a "DIR"
Post by: MichaelW on June 05, 2008, 09:38:29 AM
Lin$ux ?
Title: Re: Getting a "DIR"
Post by: jj2007 on June 05, 2008, 10:09:06 AM
Here is the absolute minimum. If M$ had the user's interest in mind, there would be a function called ExecWait with one para:

invoke ExecWait, addr PathAndCmd


Full example:
include \masm32\include\masm32rt.inc

ExecWait PROTO:DWORD

.data?
sinfo STARTUPINFO <?>
pinfo PROCESS_INFORMATION <?>

.code
PathAndCmd db "C:\WINDOWS\notepad.exe C:\WINDOWS\SYSTEM.INI", 0
AppName  db "ExecWait:", 0

start:
invoke ExecWait, addr PathAndCmd
.if eax
invoke MessageBox, 0, chr$("We are done!"), addr AppName, MB_OK
  .else
invoke MessageBox, 0, addr PathAndCmd, chr$("Something went wrong:"), MB_OK
.endif
invoke ExitProcess, 0

ExecWait proc cmdline:DWORD
  mov sinfo.cb, sizeof sinfo ; Windows needs to know the STARTUPINFO version
  mov sinfo.dwFlags, STARTF_USESHOWWINDOW ; these two are
  mov sinfo.wShowWindow, SW_MAXIMIZE ; optional but useful
  invoke CreateProcess, 0, ; no pathname
  cmdline, ; the only bit that REALLY counts...!
  0, 0, 0, ; lpProcessAttributes, lpThreadAttributes, bInheritHandles
  IDLE_PRIORITY_CLASS,
  0, 0, ; lpEnvironment, lpCurrentDirectory
  addr sinfo, addr pinfo
  mov eax, pinfo.hProcess
  .if eax
push eax
invoke WaitForSingleObject, eax, INFINITE ; you may specify milliseconds here
pop eax
  .endif
  ret
ExecWait endp
end start
Title: Re: Getting a "DIR"
Post by: sinsi on June 05, 2008, 10:25:53 AM
See, how hard was that (no, don't answer). One thing, the handles returned in pinfo can cause memory leaks (if my memory serves me well).
You need a CloseHandle for the 2 handles (even though they will address a no-longer-existing process). Hutch had quite a spirited discussion
about this a couple of years ago I think  :bdg

You could also redirect the output of the console to one of your handles (e.g. stdout -> file) and wait on that...

Your ExecWait is just a wrapper (hey, just like 'C' or 'C Double Plus Good' likes to do).
Title: Re: Getting a "DIR"
Post by: jj2007 on June 05, 2008, 10:47:05 AM
Quote from: sinsi on June 05, 2008, 10:25:53 AM
handles returned in pinfo can cause memory leaks (if my memory serves me well).
You need a CloseHandle for the 2 handles (even though they will address a no-longer-existing process).
I wonder what happens if you use the timeout (new option below): Close the handles of running processes? My test show no effect.

So here is the full "wrapper", as you like to call it. Tip: Interpret "CreateProcess" as a "wrapper" for the newbies and HL freaks, then let Olly disassemble the whole CreateProcess call starting at 7C802367, and use the listing for your own proggies. Why use a wrapper if you can make it complicated, eh?

include \masm32\include\masm32rt.inc

ExecWait PROTO:DWORD, :DWORD

.data?
sinfo STARTUPINFO <?>
pinfo PROCESS_INFORMATION <?>

.code
PathAndCmd db "C:\WINDOWS\notepad.exe C:\WINDOWS\SYSTEM.INI", 0
AppName  db "ExecWait:", 0

start:
invoke ExecWait, addr PathAndCmd, 0 ; try 2000 as timeout
.if eax
invoke MessageBox, 0, chr$("We are done!"), addr AppName, MB_OK
  .else
invoke MessageBox, 0, addr PathAndCmd, chr$("Something went wrong:"), MB_OK
.endif
invoke ExitProcess, 0

ExecWait proc cmdline:DWORD, timeout:DWORD
  mov sinfo.cb, sizeof sinfo ; Windows needs to know the STARTUPINFO version
  mov sinfo.dwFlags, STARTF_USESHOWWINDOW ; these two are
  mov sinfo.wShowWindow, SW_MAXIMIZE ; optional but useful
  invoke CreateProcess, 0, ; no pathname
  cmdline, ; the only bit that REALLY counts...!
  0, 0, 0, ; lpProcessAttributes, lpThreadAttributes, bInheritHandles
  IDLE_PRIORITY_CLASS,
  0, 0, ; lpEnvironment, lpCurrentDirectory
  addr sinfo, addr pinfo
  mov eax, pinfo.hProcess
  .if eax
push eax
.if timeout==0 ; for the lazybones who don't want to type INFINITE
mov timeout, INFINITE
.endif
invoke WaitForSingleObject, eax, timeout ; specify milliseconds here
invoke CloseHandle, pinfo.hProcess ; close the two handles
invoke CloseHandle, pinfo.hThread
pop eax
  .endif
  ret
ExecWait endp
end start
Title: Re: Getting a "DIR"
Post by: sinsi on June 05, 2008, 11:04:58 AM
For what you want 'timeout' is a waste of time since you *must* wait until the process is finished for you to get the output.

Quote from: jj2007 on June 05, 2008, 10:47:05 AM
So here is the full "wrapper", as you like to call it. Tip: Interpret "CreateProcess" as a "wrapper" for the newbies and HL freaks, then let Olly disassemble the whole CreateProcess call starting at 7C802367, and use the listing for your own proggies. Why use a wrapper if you can make it complicated, eh?
If it's not a 'wrapper' then what is?
I ain't a 'newbie' or a 'HL freak' so what's your problem? (but thanks for the 'tip' buddy)
Sure, man, it's always gonna be at 7C802367 isn't it, and 'Olly' isn't a teddy-bear i guess (I use windbg).

All this to get a f*cking directory listing, sheesh... ::)
Title: Re: Getting a "DIR"
Post by: jj2007 on June 05, 2008, 12:33:53 PM
Quote from: sinsi on June 05, 2008, 11:04:58 AM
If it's not a 'wrapper' then what is?
I ain't a 'newbie' or a 'HL freak' so what's your problem? (but thanks for the 'tip' buddy)
I guess I should mark irony in red. I didn't imply that you are a newbie. I just wanted to make the point that ExecWait should not be a 'wrapper', but rather a standard API call. Remember batch files?

echo Now we start
notepad
echo Now we are done

Compare that to the absolutely ridiculous efforts to produce the same result with CreateProcess & WaitForSingleObject. By the way, CreateProcess would not even know The Path, so you have to specify the full C:\WINDOWS\notepad.exe, hoping that it will still sit there after the next service pack. And no, %windir% won't work with CreateProcess. Holy crap.
Title: Re: Getting a "DIR"
Post by: sinsi on June 05, 2008, 01:49:13 PM
Quote from: jj2007 on June 05, 2008, 12:33:53 PMI guess I should mark irony in red.
A bit like the colour of my face...to be fair to me, I couldn't see your body language in your post. And now I've sobered up a bit I 'shore are embrassed'.
Especially since my incs are full of 'wrappers'...

As for batch files, ah, yes, the day they brought in "ECHO." was a special day.
Title: Re: Getting a "DIR"
Post by: jj2007 on June 05, 2008, 02:03:36 PM
Quote from: sinsi on June 05, 2008, 01:49:13 PM
the day they brought in "ECHO." was a special day.

Don't remember when I discovered that dot, many years ago, but yes indeed I remember it was a special day.

Re the timeout, I think it is an important option for cases when you might get stuck; e.g. launch Outlook or Word or some of these monsters, it gets blocked, your proggie is blocked, and you still have a file with valuable stuff open... that's when a 60 seconds timeout comes in handy. Even ml.exe hangs from time to time, e.g. when you specify a lot of memory in the .data? section (eventually it comes back, though).
Title: Re: Getting a "DIR"
Post by: Jimg on June 05, 2008, 03:52:06 PM
And back to the original question, if I'm going to use masmlib anyway, this works for me-

.data
  cmd2 db 'cmd /c dir "C:\help new\*.htm" /s/w/b/a-d > '
  dest db "C:allhtm.txt",0
.code
    invoke wshell,addr cmd2

can't get much simpler than that.
Title: Re: Getting a "DIR"
Post by: Vortex on June 05, 2008, 05:47:16 PM
The GeneSys project provides a shell function :

    .486                      ; create 32 bit code
    .model flat, stdcall      ; 32 bit memory model
    option casemap :none      ; case sensitive

    include \GeneSys\include\windows.inc
    include \GeneSys\include\kernel32.inc

    .code

;----------------------------------------------------------------------------;
; This is a translation of a shell script originally written by Edgar Hansen ;
; in GoASM code. Translation by Paul E. Brennick  <pebrennick@verizon.net>   ;
;                                                                            ;
; Shell:      This function will run an executable and wait until it has     ;
;             finished before continuing. The function provides for a time   ;
;             out for a maximum wait period.                                 ;
; Parameters: lpfilename = The fully qualified path to an executable to run  ;
;             dwTimeOut = The amount of time in milliseconds to wait, -1 for ;
;             no timeout                                                     ;
; Returns:    0 if successful, STATUS_TIMEOUT if the timeout has elapsed     ;
;             -1 if there was an error creating the process                  ;
;                                                                            ;
;----------------------------------------------------------------------------;
Shell PROC lpfilename:DWORD, dwTimeOut:DWORD
    LOCAL   Sh_st_info :STARTUPINFO
    LOCAL   Sh_pr_info :PROCESS_INFORMATION

    mov     DWORD PTR [Sh_st_info.cb], SIZEOF STARTUPINFO
    invoke  GetStartupInfoA, ADDR Sh_st_info
    mov     DWORD PTR [Sh_st_info.lpReserved], 0
    invoke  CreateProcessA, 0, [lpfilename], 0, 0, 0, 0, 0, 0, \
            ADDR Sh_st_info, ADDR Sh_pr_info
    test    eax, eax
    jz ERROR
    invoke  WaitForSingleObject, [Sh_pr_info.hProcess], [dwTimeOut]
    push    eax
    invoke  CloseHandle, [Sh_pr_info.hThread]
    invoke  CloseHandle, [Sh_pr_info.hProcess]
    pop     eax
    test    eax, eax
    RET
ERROR:
    xor     eax, eax
    sub     eax, 1
    RET
Shell ENDP

    end
Title: Re: Getting a "DIR"
Post by: jj2007 on June 06, 2008, 06:37:00 AM
Looks good, Vortex. Main difference to my version is that you call GetStartupInfo (which in turn allows to declare the STARTUPINFO locally). How much is the added value? MSDN has some pretty original remarks on this function:

This function does not return a value. (if you find one, throw it away!)

If an error occurs, the ANSI version of this function (GetStartupInfoA) can raise an exception (but we won't tell you when, haha!). The Unicode version (GetStartupInfoW) does not fail. (never! promised!)
Title: Re: Getting a "DIR"
Post by: sinsi on June 06, 2008, 07:13:17 AM
For all that code to shell out to a cmd prompt, why not this code

allspec dd '*'
updir   dd '..'

search_dir proc uses ebx esi
local wfd:WIN32_FIND_DATA
lea esi,wfd
invoke FindFirstFile,offset allspec,esi
cmp eax,-1
jz done
mov ebx,eax
  next: call found
        test wfd.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
jz over
        cmp wfd.cFileName,'.'
        jz over
invoke SetCurrentDirectory,addr wfd.cFileName
test eax,eax
jz fini
call search_dir
invoke SetCurrentDirectory,offset updir
  over:
invoke FindNextFile,ebx,esi
test eax,eax
jnz next
  fini: invoke FindClose,ebx
  done: ret
search_dir endp

The only other code needed is the 'found' proc called (oh, and some error checking)
Title: Re: Getting a "DIR"
Post by: hutch-- on June 06, 2008, 07:25:06 AM
I am inclided to agree with sinsi here, for the amount of code to shell out to CMD.EXE or command.com, you may as well get the data yourself.
Title: Re: Getting a "DIR"
Post by: jj2007 on June 06, 2008, 09:13:44 AM
Here is a version that returns success in eax plus the exit code in ecx. It seems the topic has moved to "what is the best version of SHELL" ;-)

include \masm32\include\masm32rt.inc

ShellWait PROTO: DWORD, :SDWORD

.code

PathAndCmd db "C:\WINDOWS\notepad.exe C:\WINDOWS\SYSTEM.INI", 0
AppName  db "ExecWait:", 0

start: invoke ShellWait, addr PathAndCmd, 0 ; try 2000 as timeout
.if eax
invoke MessageBox, 0, str$(ecx), chr$("We are done with this exit code:"), MB_OK
  .else
invoke MessageBox, 0, addr PathAndCmd, chr$("Something went wrong:"), MB_OK
.endif
invoke ExitProcess, 0

ShellWait proc cmdline:DWORD, timeout:SDWORD
LOCAL ExCode:DWORD ; exit code, returned in ecx
LOCAL sinfo:STARTUPINFO
LOCAL pinfo:PROCESS_INFORMATION
  mov sinfo.cb, sizeof sinfo ; Windows needs to know the STARTUPINFO version
  mov sinfo.dwFlags, STARTF_USESHOWWINDOW ; these two are
  mov sinfo.wShowWindow, SW_MAXIMIZE ; optional but useful
  invoke GetStartupInfo, addr sinfo ; fill the structure
  xor ecx, ecx ; using ecx as NULL saves some bytes
  mov ExCode, ecx ; valid only if eax!=0
  invoke CreateProcess, ecx, ; no pathname
  cmdline, ; the only bit that REALLY counts...!
  ecx, ecx, ecx, ; lpProcessAttributes, lpThreadAttributes, bInheritHandles
  ecx, ecx, ecx, ; xx_PRIORITY_CLASS, lpEnvironment, lpCurrentDirectory
  addr sinfo, addr pinfo
  mov eax, pinfo.hProcess
  .if eax
push eax
.if timeout<=0 ; for those lazybones who don't want to type INFINITE
mov timeout, INFINITE ; -1
.endif
invoke WaitForSingleObject, eax, timeout ; specify milliseconds here
invoke GetExitCodeProcess, pinfo.hProcess, addr ExCode
invoke CloseHandle, pinfo.hProcess
invoke CloseHandle, pinfo.hThread
pop eax ; hProcess
  .endif
  mov ecx, ExCode
  ret
ShellWait endp   ; <-- Edit
end start


Title: Re: Getting a "DIR"
Post by: jj2007 on June 07, 2008, 12:03:35 AM
  mov sinfo.dwFlags, STARTF_USESHOWWINDOW    ; these two are
  mov sinfo.wShowWindow, SW_MAXIMIZE          ; optional but useful


I could swear these flags worked on my office puter with XP SP2. But now it's weekend, and on XP SP1 the flags are being happily ignored by Notepad. When started from a Desktop shortcut, they work, though. Windows mysteries...
Title: Re: Getting a "DIR"
Post by: jj2007 on June 09, 2008, 08:22:47 AM
Quote from: jj2007 on June 07, 2008, 12:03:35 AM
  mov sinfo.dwFlags, STARTF_USESHOWWINDOW    ; these two are
  mov sinfo.wShowWindow, SW_MAXIMIZE          ; optional but useful


I could swear these flags worked on my office puter with XP SP2. But now it's weekend, and on XP SP1 the flags are being happily ignored by Notepad. When started from a Desktop shortcut, they work, though. Windows mysteries...

No Windows mysteries: jj lacked sleep.

Old:
LOCAL pinfo:PROCESS_INFORMATION
  mov sinfo.cb, sizeof sinfo ; Windows needs to know the STARTUPINFO version
  mov sinfo.dwFlags, STARTF_USESHOWWINDOW ; these two are
  mov sinfo.wShowWindow, SW_MAXIMIZE ; optional but useful
  invoke GetStartupInfo, addr sinfo ; fill the structure

New:
LOCAL pinfo:PROCESS_INFORMATION
  invoke GetStartupInfo, addr sinfo ; fill the structure, then change the two members:
  ; NO mov sinfo.cb, sizeof sinfo ; Windows hopefully knows the STARTUPINFO version
  mov sinfo.dwFlags, STARTF_USESHOWWINDOW ; these two are
  mov sinfo.wShowWindow, SW_MAXIMIZE ; optional but useful

After all, I found "invoke ShellWait, addr PathAndCmd, 0" far too clumsy, so I added a macro, see attachment. Usage:

.data
PathAndCmd   db "C:\WINDOWS\notepad.exe C:\WINDOWS\SYSTEM.INI", 0

.code
start:
   Launch PathAndCmd      ; plain launch
   Launch PathAndCmd, SW_MAXIMIZE      ; gimme a full screen
   Launch PathAndCmd, SW_HIDE, 2000      ; do it hidden, and come back soon


[attachment deleted by admin]
Title: Re: Getting a "DIR"
Post by: Jimg on December 28, 2010, 03:33:58 PM
The simplest, laziest way is wshell-

.data
  cmd2 db 'cmd /c dir "C:\help new\*.htm" /s/w/b/a-d > ' ; or whatever options you want
  dest db "C:allhtm.txt",0
.code
    invoke wshell,addr cmd2


Title: Re: Getting a "DIR"
Post by: jj2007 on December 28, 2010, 04:05:45 PM
Quote from: Jimg on December 28, 2010, 03:33:58 PM
The simplest, laziest way is wshell-

Jim,
You realise this thread is a bit old, do you? :wink
But it works indeed. The strange thing is that MSDN is denying the existence of wshell...