How to get string from ReadProcessMemory for any progam - vb - vb6

Can you give me an example of how to get output of ReadProcessMemory (in vb)
For example, i want to extract all the values of the ReadProcessMemory for any program .. then put it in a text file.

ReadProcessMemory is seldom used alone since the memory address has to come from somewhere. I don't have the code for dumping a process either, but here is an example for reading the command line of the process using native API ZwQueryInformationProcess
In this example, GetProcessCommandLine uses ZwQueryInformationProcess to retrieve the PEB of the given process, and then looks for the command line in the process memory.
Option Explicit
Public Declare Function ZwQueryInformationProcess Lib "NTDLL.DLL" (ByVal ProcessHandle As Long, ByVal InformationClass As PROCESSINFOCLASS, ByRef ProcessInformation As Any, ByVal ProcessInformationLength As Long, ByRef ReturnLenght As Long) As Long
Public Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Public Enum PROCESSINFOCLASS
ProcessBasicInformation
ProcessQuotaLimits
ProcessIoCounters
ProcessVmCounters
ProcessTimes
ProcessBasePriority
ProcessRaisePriority
ProcessDebugPort
ProcessExceptionPort
ProcessAccessToken
ProcessLdtInformation
ProcessLdtSize
ProcessDefaultHardErrorMode
ProcessIoPortHandlers '// Note: this is kernel mode only
ProcessPooledUsageAndLimits
ProcessWorkingSetWatch
ProcessUserModeIOPL
ProcessEnableAlignmentFaultFixup
ProcessPriorityClass
ProcessWx86Information
ProcessHandleCount
ProcessAffinityMask
ProcessPriorityBoost
ProcessDeviceMap
ProcessSessionInformation
ProcessForegroundInformation
ProcessWow64Information
ProcessImageFileName
ProcessLUIDDeviceMapsEnabled
ProcessBreakOnTermination
ProcessDebugObjectHandle
ProcessDebugFlags
ProcessHandleTracing
ProcessIoPriority
ProcessExecuteFlags
ProcessResourceManagement
ProcessCookie
ProcessImageInformation
MaxProcessInfoClass '// MaxProcessInfoClass should always be the last enum
End Enum
Public Type PROCESS_BASIC_INFORMATION
ExitStatus As Long
PebBaseAddress As Long
AffinityMask As Long
BasePriority As Long
UniqueProcessId As Long
InheritedFromUniqueProcessId As Long
End Type
Public Function GetProcessCommandLine(ByVal hProcess As Long) As String
Dim NTSTATUS As Long
Dim objBasic As PROCESS_BASIC_INFORMATION
Dim objBaseAddress As Long
Dim bytName() As Byte
Dim strModuleName As String
Dim obj As Long
Dim dwSize As Long
If hProcess = 0 Then
GetProcessCommandLine = ""
Exit Function
End If
Dim lngRet As Long, lngReturn As Long
NTSTATUS = ZwQueryInformationProcess(hProcess, ProcessBasicInformation, objBasic, Len(objBasic), dwSize)
If (NTSTATUS = 0) Then
ReadProcessMemory hProcess, ByVal objBasic.PebBaseAddress + &H10, obj, 4, lngRet
If lngRet <> 4 Then Exit Function
ReadProcessMemory hProcess, ByVal obj + &H40, dwSize, 2, lngRet
If lngRet <> 2 Then Exit Function
ReadProcessMemory hProcess, ByVal obj + &H44, obj, 4, lngRet
If lngRet <> 4 Then Exit Function
ReDim bytName(dwSize - 1)
ReadProcessMemory hProcess, ByVal obj, bytName(0), dwSize, lngRet
If lngRet <> dwSize Then Exit Function
GetProcessCommandLine = bytName
End If
End Function

Related

gcc Bad File Descriptor Error When Piping Input Into It

I figured out that you can compile c code with gcc from stdin with gcc -x c - and the command cat main.c | gcc -x c - sucesfully compiles it.
So, i have a vb6 application that has c code in a string, and i want to pipe that c code into gcc with win32 api, i have mingw installed btw.
The following basic code creates the input pipe and invokes gcc, However immediatly after CreateProcess the following is printed to the console:
cc1.exe: fatal error: stdout: Bad file descriptor
compilation terminated.
That is, that error message is printed before i put anything into the input pipe
Private Declare Function CreatePipe Lib "kernel32" (phReadPipe As Long, phWritePipe As Long, lpPipeAttributes As SECURITY_ATTRIBUTES, ByVal nSize As Long) As Long
Private Declare Function SetHandleInformation Lib "kernel32" (ByVal hObject As Long, ByVal dwMask As Long, ByVal dwFlags As Long) As Long
Private Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessW" (ByVal lpApplicationName As Long, ByVal lpCommandLine As Long, ByVal lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, ByVal lpEnvironment As Long, ByVal lpCurrentDriectory As Long, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, ByVal lpBuffer As Long, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, Optional ByVal lpOverlapped As Long = 0) As Long
Private Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long, ByVal lpBuffer As Long, ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten As Long, Optional ByVal lpOverlapped As Long = 0) As Long
Private Declare Function GetStdHandle Lib "kernel32" (ByVal nStdHandle As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function AllocConsole Lib "kernel32" () As Long
Private Declare Function FreeConsole Lib "kernel32" () As Long
Private Declare Function GetLastError Lib "kernel32" () As Long
Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type
Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Const HANDLE_FLAG_INHERIT As Long = 1
Const STARTF_USESTDHANDLES As Long = &H100
Public Function Execute(CmdLine As String) As Boolean
ExecuteWithStdIn "gcc -x c -", "#error if gcc receives this code it shoud complain about this error directive"
End Function
'Pipes String StdIn into The Stdin Of The Child Process
'Output Of Child Process Is Redirected Into My StdOut
Public Function ExecuteWithStdIn(CmdLine As String, StdIn As String) As Boolean
Dim sAttr As SECURITY_ATTRIBUTES
Dim E As Long
Dim stdin_read As Long
Dim stdin_write As Long
AllocConsole
sAttr.nLength = LenB(sAttr)
sAttr.bInheritHandle = 1
E = CreatePipe(stdin_read, stdin_write, sAttr, 0)
E = SetHandleInformation(stdin_read, HANDLE_FLAG_INHERIT, 0)
Dim CmdLine As String
Dim Proci As PROCESS_INFORMATION 'all 0s
Dim Sti As STARTUPINFO
Sti.cb = LenB(Sti)
Sti.hStdError = GetStdHandle(-11)
Sti.hStdOutput = GetStdHandle(-11) 'Piping gcc output to my output
Sti.hStdInput = stdin_read 'Capturing the input so we may pipe the c code
Sti.dwFlags = STARTF_USESTDHANDLES
E = CreateProcess(0, StrPtr(CmdLine), 0, 0, 1, 0, 0, 0, Sti, Proci)
'### When The Code Reaches Here, E = 1 (Success) And gcc has already
'### outputted to stdout that Bad File Descriptor error message
E = GetLastError
Dim Buffer() As Byte
Dim BufferPtr As Long
Dim BufferSize As Long
Dim BytesWritten As Long
Let Buffer = StrConv(StdIn, vbFromUnicode)
BufferPtr = VarPtr(Buffer(0))
BufferSize = UBound(Buffer)
Do
If BufferSize > 4096 Then
E = WriteFile(stdin_write, BufferPtr, 4096, BytesWritten)
Else
E = WriteFile(stdin_write, BufferPtr, BufferSize, BytesWritten)
End If
If E = 0 Then Exit Do
BufferPtr = BufferPtr + BytesWritten
BufferSize = BufferSize - BytesWritten
Loop While BufferSize > 0
E = CloseHandle(stdin_read)
FreeConsole
End Function
Can your stdout be inherited by child process ? Actually, it needs. the same as stderr.
Sti.hStdOutput = GetStdHandle(-11) 'Piping gcc output to my output
You can refer to the following C++ code which works for me:
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
SECURITY_ATTRIBUTES s;
s.nLength = sizeof(SECURITY_ATTRIBUTES);
s.bInheritHandle = TRUE;
s.lpSecurityDescriptor = NULL;
HANDLE hin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, &s, OPEN_EXISTING, 0, 0);
SetStdHandle(STD_INPUT_HANDLE, hin);
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
//siStartInfo.hStdInput = hin;
//siStartInfo.hStdInput = NULL;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bSuccess = CreateProcess(NULL,
szCmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
if (!bSuccess)
ErrorExit(TEXT("CreateProcess"));
Thanks so much #YangXiaoPro for your answer, your comment on handle inheritance led me to figure out that this line:
SetHandleInformation(stdin_read, HANDLE_FLAG_INHERIT, 0)
that stops the pipe reader from being inherited, is wrong. It should instead stop the pipe writer from being inherited
SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0) 'fixed

Run an external process without freezing main UI in vb6

I have VB6 Application which depends upon another EXE file which is invoked via CreateProcess from lib kernel32 and pipe connection to fetch the output.
How can I invoke it asynchronously in my main form without freezing the UI?
Currently, Form1 Freezes when the external application takes a longer time to respond.
Attribute VB_Name = "CmdOutput"
Option Explicit
''''''''''''''''''''''''''''''''''''''''
' Joacim Andersson, Brixoft Software
' http://www.brixoft.net
''''''''''''''''''''''''''''''''''''''''
' STARTUPINFO flags
Private Const STARTF_USESHOWWINDOW = &H1
Private Const STARTF_USESTDHANDLES = &H100
' ShowWindow flags
Private Const SW_HIDE = 0
' DuplicateHandle flags
Private Const DUPLICATE_CLOSE_SOURCE = &H1
Private Const DUPLICATE_SAME_ACCESS = &H2
' Error codes
Private Const ERROR_BROKEN_PIPE = 109
Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type
Private Declare Function CreatePipe _
Lib "kernel32" ( _
phReadPipe As Long, _
phWritePipe As Long, _
lpPipeAttributes As Any, _
ByVal nSize As Long) As Long
Private Declare Function ReadFile _
Lib "kernel32" ( _
ByVal hFile As Long, _
lpBuffer As Any, _
ByVal nNumberOfBytesToRead As Long, _
lpNumberOfBytesRead As Long, _
lpOverlapped As Any) As Long
Private Declare Function CreateProcess _
Lib "kernel32" Alias "CreateProcessA" ( _
ByVal lpApplicationName As String, _
ByVal lpCommandLine As String, _
lpProcessAttributes As Any, _
lpThreadAttributes As Any, _
ByVal bInheritHandles As Long, _
ByVal dwCreationFlags As Long, _
lpEnvironment As Any, _
ByVal lpCurrentDriectory As String, _
lpStartupInfo As STARTUPINFO, _
lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare Function GetCurrentProcess _
Lib "kernel32" () As Long
Private Declare Function DuplicateHandle _
Lib "kernel32" ( _
ByVal hSourceProcessHandle As Long, _
ByVal hSourceHandle As Long, _
ByVal hTargetProcessHandle As Long, _
lpTargetHandle As Long, _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwOptions As Long) As Long
Private Declare Function CloseHandle _
Lib "kernel32" ( _
ByVal hObject As Long) As Long
Private Declare Function OemToCharBuff _
Lib "user32" Alias "OemToCharBuffA" ( _
lpszSrc As Any, _
ByVal lpszDst As String, _
ByVal cchDstLength As Long) As Long
' Function GetCommandOutput
'
' sCommandLine: [in] Command line to launch
' blnStdOut [in,opt] True (defualt) to capture output to STDOUT
' blnStdErr [in,opt] True to capture output to STDERR. False is default.
' blnOEMConvert: [in,opt] True (default) to convert DOS characters to Windows, False to skip conversion
'
' Returns: String with STDOUT and/or STDERR output
'
Public Function GetCommandOutput( _
sCommandLine As String, _
Optional blnStdOut As Boolean = True, _
Optional blnStdErr As Boolean = False, _
Optional blnOEMConvert As Boolean = True _
) As String
Dim hPipeRead As Long, hPipeWrite1 As Long, hPipeWrite2 As Long
Dim hCurProcess As Long
Dim sa As SECURITY_ATTRIBUTES
Dim si As STARTUPINFO
Dim pi As PROCESS_INFORMATION
Dim baOutput() As Byte
Dim sNewOutput As String
Dim lBytesRead As Long
Dim fTwoHandles As Boolean
Dim lRet As Long
Const BUFSIZE = 1024 ' pipe buffer size
' At least one of them should be True, otherwise there's no point in calling the function
If (Not blnStdOut) And (Not blnStdErr) Then
Err.Raise 5 ' Invalid Procedure call or Argument
End If
' If both are true, we need two write handles. If not, one is enough.
fTwoHandles = blnStdOut And blnStdErr
ReDim baOutput(BUFSIZE - 1) As Byte
With sa
.nLength = Len(sa)
.bInheritHandle = 1 ' get inheritable pipe handles
End With
If CreatePipe(hPipeRead, hPipeWrite1, sa, BUFSIZE) = 0 Then
Exit Function
End If
hCurProcess = GetCurrentProcess()
' Replace our inheritable read handle with an non-inheritable. Not that it
' seems to be necessary in this case, but the docs say we should.
Call DuplicateHandle(hCurProcess, hPipeRead, hCurProcess, hPipeRead, 0&, _
0&, DUPLICATE_SAME_ACCESS Or DUPLICATE_CLOSE_SOURCE)
' If both STDOUT and STDERR should be redirected, get an extra handle.
If fTwoHandles Then
Call DuplicateHandle(hCurProcess, hPipeWrite1, hCurProcess, hPipeWrite2, 0&, _
1&, DUPLICATE_SAME_ACCESS)
End If
With si
.cb = Len(si)
.dwFlags = STARTF_USESHOWWINDOW Or STARTF_USESTDHANDLES
.wShowWindow = SW_HIDE ' hide the window
If fTwoHandles Then
.hStdOutput = hPipeWrite1
.hStdError = hPipeWrite2
ElseIf blnStdOut Then
.hStdOutput = hPipeWrite1
Else
.hStdError = hPipeWrite1
End If
End With
If CreateProcess(vbNullString, sCommandLine, ByVal 0&, ByVal 0&, 1, 0&, _
ByVal 0&, vbNullString, si, pi) Then
' Close thread handle - we don't need it
Call CloseHandle(pi.hThread)
' Also close our handle(s) to the write end of the pipe. This is important, since
' ReadFile will *not* return until all write handles are closed or the buffer is full.
Call CloseHandle(hPipeWrite1)
hPipeWrite1 = 0
If hPipeWrite2 Then
Call CloseHandle(hPipeWrite2)
hPipeWrite2 = 0
End If
Do
' Add a DoEvents to allow more data to be written to the buffer for each call.
' This results in fewer, larger chunks to be read.
'DoEvents
If ReadFile(hPipeRead, baOutput(0), BUFSIZE, lBytesRead, ByVal 0&) = 0 Then
Exit Do
End If
If blnOEMConvert Then
' convert from "DOS" to "Windows" characters
sNewOutput = String$(lBytesRead, 0)
Call OemToCharBuff(baOutput(0), sNewOutput, lBytesRead)
Else
' perform no conversion (except to Unicode)
sNewOutput = Left$(StrConv(baOutput(), vbUnicode), lBytesRead)
End If
GetCommandOutput = GetCommandOutput & sNewOutput
' If you are executing an application that outputs data during a long time,
' and don't want to lock up your application, it might be a better idea to
' wrap this code in a class module in an ActiveX EXE and execute it asynchronously.
' Then you can raise an event here each time more data is available.
'RaiseEvent OutputAvailabele(sNewOutput)
Loop
' When the process terminates successfully, Err.LastDllError will be
' ERROR_BROKEN_PIPE (109). Other values indicates an error.
Call CloseHandle(pi.hProcess)
Else
GetCommandOutput = "Failed to create process, check the path of the command line."
End If
' clean up
Call CloseHandle(hPipeRead)
If hPipeWrite1 Then
Call CloseHandle(hPipeWrite1)
End If
If hPipeWrite2 Then
Call CloseHandle(hPipeWrite2)
End If
End Function
As the code comment in the code you linked suggests, you could pull this out-of-process into an ActiveX exe to wrap this.
To keep it in-process, you would need to use MsgWaitForMultipleObjects (https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-msgwaitformultipleobjects).
Google located a VB6 sample: http://www.freevbcode.com/ShowCode.asp?ID=4322
The idea here is that the function will wait until either data is available on the handle OR there is a UI event (in which case DoEvents is used to dispatch).

Function ExecCmd, used to work in access 2007 but no more in access 2013 (64bit)

I used to be able to run command lines running external programs (like exiftool or image magick) with the function below in my access 2007 db.
I migrated to access 2013 and after a few code adaptations, the DB works, except this function ExecCmd. When I use it I get no error but nothing happens.
Can anyone help ? Either by showing me whats wrong or suggesting a better way to do the same.
Public Const SEE_MASK_DOENVSUBST As Long = &H200
Public Const SEE_MASK_IDLIST As Long = &H4
Public Const SEE_MASK_NOCLOSEPROCESS As Long = &H40
Public Const SW_HIDE As Long = 0
Public Const SW_SHOW As Long = 5
Public Const WAIT_TIMEOUT As Long = 258&
Public Type SHELLEXECUTEINFOA
cbSize As Long
fMask As Long
hwnd As Long
lpVerb As String
lpFile As String
lpParameters As String
lpDirectory As String
nShow As Long
hInstApp As Long
lpIDList As Long
lpClass As String
hkeyClass As Long
dwHotKey As Long
hIcon As Long
hProcess As Long
End Type
Public Declare PtrSafe Function CloseHandle Lib "kernel32.dll" (ByVal hObject As Long) As Long
Public Declare PtrSafe Function GetExitCodeProcess Lib "kernel32.dll" (ByVal hProcess As Long, ByRef lpExitCode As Long) As Long
Public Declare PtrSafe Function ShellExecuteEx Lib "shell32.dll" (ByRef lpExecInfo As SHELLEXECUTEINFOA) As Long
Public Declare PtrSafe Function WaitForSingleObject Lib "kernel32.dll" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
Public Function ExecCmd(ByVal vsCmdLine As String, Optional ByRef vsParameters As String, Optional ByRef vsCurrentDirectory As String = vbNullString, Optional ByVal vnShowCmd As Long = SW_SHOW, Optional ByVal vnTimeOut As Long = 200) As Long
Dim lpShellExInfo As SHELLEXECUTEINFOA
With lpShellExInfo
.cbSize = Len(lpShellExInfo)
.lpDirectory = vsCurrentDirectory
.lpVerb = "open"
.lpFile = vsCmdLine
.lpParameters = vsParameters
.nShow = vnShowCmd
.fMask = SEE_MASK_DOENVSUBST Or SEE_MASK_NOCLOSEPROCESS Or SEE_MASK_IDLIST
End With
If ShellExecuteEx(lpShellExInfo) Then
Do While WaitForSingleObject(lpShellExInfo.hProcess, vnTimeOut) = WAIT_TIMEOUT
DoEvents
Loop
GetExitCodeProcess lpShellExInfo.hProcess, ExecCmd
CloseHandle lpShellExInfo.hProcess
Else
ExecCmd = vbError
End If
End Function
I found another similar function, but the first one was better, if only because It had the ability to run the command hidden.
This works:
Option Explicit
Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessID As Long
dwThreadID As Long
End Type
Private Declare PtrSafe Function WaitForSingleObject Lib "kernel32" (ByVal _
hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare PtrSafe Function CreateProcessA Lib "kernel32" (ByVal _
lpApplicationName As Long, ByVal lpCommandLine As String, ByVal _
lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As Long, _
lpStartupInfo As STARTUPINFO, lpProcessInformation As _
PROCESS_INFORMATION) As Long
Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal _
hObject As Long) As Long
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const INFINITE = -1&
Public Sub ExecCmd(cmdline As String)
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
Dim ReturnValue As Integer
' Initialize the STARTUPINFO structure:
start.cb = Len(start)
' Start the shelled application:
ReturnValue = CreateProcessA(0&, cmdline$, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
' Wait for the shelled application to finish:
Do
ReturnValue = WaitForSingleObject(proc.hProcess, 0)
DoEvents
Loop Until ReturnValue <> 258
ReturnValue = CloseHandle(proc.hProcess)
End Sub
I was able to recreate your issue using a simple test case. The VBA procedure...
Sub test()
Dim r As Variant
r = ExecCmd("cscript.exe", "C:\Users\Public\Documents\foo.vbs", "", 0)
End Sub
...worked fine under 32-bit Access 2013 but failed silently under 64-bit Access 2013. However, the following code does seem to work under 64-bit Access 2013:
Sub test2()
Dim sh As Object ' WshShell
Set sh = CreateObject("WScript.Shell")
sh.Run "cscript.exe C:\Users\Public\Documents\foo.vbs", 0
Set sh = Nothing
End Sub
For more information, see
Run Method (Windows Script Host)
Problem solved: API calls in 64bit are different.
Code below works: appli is launched and the code waits for it to finish before going further.
Best of all: a parameter controls the visibility of the app window: very useful to run a background batch of command line processes without poisoning the display or the focus.
Thanks for the help !
Private Const STARTF_USESHOWWINDOW& = &H1
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const INFINITE = -1&
Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessID As Long
dwThreadID As Long
End Type
Private Declare PtrSafe Function WaitForSingleObject Lib "kernel32" (ByVal _
hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare PtrSafe Function CreateProcessA Lib "kernel32" (ByVal _
lpApplicationName As Long, ByVal lpCommandLine As String, ByVal _
lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As Long, _
lpStartupInfo As STARTUPINFO, lpProcessInformation As _
PROCESS_INFORMATION) As Long
Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal _
hObject As Long) As Long
Public Sub ShellWait(Pathname As String, Optional WindowStyle As Long)
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
Dim ret As Long
' Initialize the STARTUPINFO structure:
With start
.cb = Len(start)
If Not IsMissing(WindowStyle) Then
.dwFlags = STARTF_USESHOWWINDOW
.wShowWindow = WindowStyle
End If
End With
' Start the shelled application:
ret& = CreateProcessA(0&, Pathname, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
' Wait for the shelled application to finish:
ret& = WaitForSingleObject(proc.hProcess, INFINITE)
ret& = CloseHandle(proc.hProcess)
End Sub

WaitForSingleObject is timing out when returned data is over 4150 bytes

Using VBA 6.0 and WaitForSingleObject. I'm relatively new to VBA 6 so I'm having this problem. Unfortunately, I cannot upgrade the project to VS 2010. I am creating a pipe out to a cmd shell and passing a command line then waiting for the results. If I run the exact command as it is being sent, from a cmd window it works perfectly and the errorlevel always returns 0. But running the commands with WaitForSingleObject returns zero when the returned data is less than 4151 bytes and times out with a 258 error if it's 4151 or more.
The timeout has been increased to 60 seconds and does not make a difference. If it is set to infinite, it never moves forward (I've let it sit for hours). The failing commands, when run from cmd, finish the output in about a second. Here's the full code (the error handling is commented out just so that I can see what data is returned. It does show the first 4150 bytes of data.):
Option Explicit
Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type
Private Type STARTUPINFO
cb As Long
lpReserved As Long
lpDesktop As Long
lpTitle As Long
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Byte
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Const WAIT_LONG As Long = 60000
Private Const WAIT_INFINITE As Long = (-1&)
Private Const STARTF_USESHOWWINDOW As Long = &H1
Private Const STARTF_USECOUNTCHARS As Long = &H8
Private Const STARTF_USESTDHANDLES As Long = &H100
Private Const SW_HIDE As Long = 0&
Private Const SW_SHOWNORMAL As Long = 1
Private Declare Function CreatePipe Lib "kernel32" (phReadPipe As Long, phWritePipe As Long, lpPipeAttributes As SECURITY_ATTRIBUTES, ByVal nSize As Long) As Long
Private Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As Long, ByVal lpCommandLine As String, lpProcessAttributes As Any, lpThreadAttributes As Any, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, lpEnvironment As Any, ByVal lpCurrentDriectory As String, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, lpOverlapped As Any) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, lpExitCode As Long) As Long
Private Declare Sub GetStartupInfo Lib "kernel32" Alias "GetStartupInfoA" (lpStartupInfo As STARTUPINFO)
Private Declare Function GetFileSize Lib "kernel32" (ByVal hFile As Long, lpFileSizeHigh As Long) As Long
Public Function Redirect(szBinaryPath As String, szCommandLn As String) As String
Dim tSA_CreatePipe As SECURITY_ATTRIBUTES
Dim tSA_CreateProcessPrc As SECURITY_ATTRIBUTES
Dim tSA_CreateProcessThrd As SECURITY_ATTRIBUTES
Dim tSA_CreateProcessPrcInfo As PROCESS_INFORMATION
Dim tStartupInfo As STARTUPINFO
Dim hRead As Long
Dim hWrite As Long
Dim bRead As Long
Dim abytBuff() As Byte
Dim lngResult As Long
Dim szFullCommand As String
Dim lngExitCode As Long
Dim lngSizeOf As Long
Dim intReturn As Integer
tSA_CreatePipe.nLength = Len(tSA_CreatePipe)
tSA_CreatePipe.lpSecurityDescriptor = 0&
tSA_CreatePipe.bInheritHandle = True
tSA_CreateProcessPrc.nLength = Len(tSA_CreateProcessPrc)
tSA_CreateProcessThrd.nLength = Len(tSA_CreateProcessThrd)
If (CreatePipe(hRead, hWrite, tSA_CreatePipe, 0&) <> 0&) Then
tStartupInfo.cb = Len(tStartupInfo)
GetStartupInfo tStartupInfo
With tStartupInfo
.hStdOutput = hWrite
.hStdError = hWrite
.dwFlags = STARTF_USESHOWWINDOW Or STARTF_USESTDHANDLES
.wShowWindow = SW_HIDE
End With
szFullCommand = """" & szBinaryPath & """" & " " & szCommandLn
frmCszKUpNS.FullCommand.Text = szFullCommand
lngResult = CreateProcess(0&, szFullCommand, tSA_CreateProcessPrc, tSA_CreateProcessThrd, _
True, 0&, 0&, vbNullString, tStartupInfo, tSA_CreateProcessPrcInfo)
If (lngResult <> 0&) Then
lngResult = WaitForSingleObject(tSA_CreateProcessPrcInfo.hProcess, WAIT_LONG)
lngSizeOf = GetFileSize(hRead, 0&)
If (lngSizeOf > 0) Then
ReDim abytBuff(lngSizeOf - 1)
If ReadFile(hRead, abytBuff(0), UBound(abytBuff) + 1, bRead, ByVal 0&) Then
Redirect = StrConv(abytBuff, vbUnicode)
End If
End If
Call GetExitCodeProcess(tSA_CreateProcessPrcInfo.hProcess, lngExitCode)
CloseHandle tSA_CreateProcessPrcInfo.hThread
CloseHandle tSA_CreateProcessPrcInfo.hProcess
'If (lngExitCode <> 0&) Then Err.Raise vbObject + 1235&, "GetExitCodeProcess", "Non-zero Application exist code"
CloseHandle hWrite
CloseHandle hRead
Else
'Err.Raise vbObject + 1236&, "CreateProcess", "CreateProcess Failed, Code: " & Err.LastDllError
End If
End If
End Function
Pipes are buffered, but there's a limit to the buffering. When the buffer is full, the one writing to the pipe is blocked until more space is available in the buffer. Space becomes available when the pipe is read from.
Since you don't read anything from the pipe until the writer terminates, and the writer is blocked waiting for a read to occur, you have deadlock, at least until you time out. You've evidently discovered the size of the pipe's buffer.
One solution is to not wait for the writing process to terminate. Instead, just start reading, and you'll get data from the pipe as it becomes available. If you spend too much time reading, and the pipe hasn't dried up, then you can give up and conclude that the program is taking too long.

CreateProcess and strange nslookup error

I have this api routine that I use regularly to capture dos output. Recently a strange bug has been found where it doesn't seem to allow dns calls. For instance nslookup will return the "No response from server" error with Server: UnKnown. Ping will work if you supply it an IP address, but not if it has to make a dns call. This problem is completely isolated to this code.
Any insight into this problem would be appreciated. Winapi isn't my strongest area.
Edit: Sorry for adding all of the constants and types, but I made this into something you can paste into a module and run to test for yourself in an effort to make the problem easier to solve.
' STARTUPINFO flags
Private Const STARTF_USESHOWWINDOW = &H1
Private Const STARTF_USESTDHANDLES = &H100
' ShowWindow flag
Private Const SW_HIDE = 0
'CreatePipe buffer size
Private Const BUFSIZE = 1024
Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Private Type STARTUPINFO
cb As Long
lpReserved As Long
lpDesktop As Long
lpTitle As Long
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type
Private Declare Function CloseHandle Lib "kernel32.dll" (ByVal hObject As Long) As Long
Private Declare Function CreatePipe Lib "kernel32.dll" (ByRef phReadPipe As Long, ByRef phWritePipe As Long, ByRef lpPipeAttributes As SECURITY_ATTRIBUTES, ByVal nSize As Long) As Long
Private Declare Function CreateProcess Lib "kernel32.dll" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, ByRef lpProcessAttributes As SECURITY_ATTRIBUTES, ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, ByRef lpEnvironment As Any, ByVal lpCurrentDriectory As String, ByRef lpStartupInfo As STARTUPINFO, ByRef lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare Sub GetStartupInfo Lib "kernel32.dll" Alias "GetStartupInfoA" (ByRef lpStartupInfo As STARTUPINFO)
Private Declare Function PeekNamedPipe Lib "kernel32.dll" (ByVal hNamedPipe As Long, ByRef lpBuffer As Any, ByVal nBufferSize As Long, ByRef lpBytesRead As Long, ByRef lpTotalBytesAvail As Long, ByRef lpBytesLeftThisMessage As Long) As Long
Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, ByVal lpBuffer As String, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, lpOverlapped As Any) As Long
Sub CreateprocessApiTest()
On Error GoTo errHandler
Dim pa As SECURITY_ATTRIBUTES
Dim pra As SECURITY_ATTRIBUTES
Dim tra As SECURITY_ATTRIBUTES
Dim si As STARTUPINFO
Dim pi As PROCESS_INFORMATION
Dim retVal As Long
Dim command As String
Dim ErrorDesc As String
Dim hRead As Long ' stdout + stderr
Dim hWrite As Long
Dim bAvail As Long ' pipe bytes available (PeekNamedPipe)
Dim bRead As Long ' pipe bytes fetched (ReadFile)
Dim bString As String ' our buffer
Dim s As String
command = "nslookup google.com"
pa.nLength = Len(pa)
pa.bInheritHandle = 1
pra.nLength = Len(pra)
tra.nLength = Len(tra)
retVal = CreatePipe(hRead, hWrite, pa, BUFSIZE)
With si
.cb = Len(si)
GetStartupInfo si
.dwFlags = STARTF_USESHOWWINDOW Or STARTF_USESTDHANDLES
.wShowWindow = SW_HIDE
.hStdOutput = hWrite
.hStdError = hWrite
End With
retVal = CreateProcess(vbNullString, command, pra, tra, 1, 0&, 0&, vbNullString, si, pi)
Do While PeekNamedPipe(hRead, ByVal 0, 0, ByVal 0, bAvail, ByVal 0)
DoEvents
If bAvail Then
bString = String(bAvail, 0)
ReadFile hRead, bString, bAvail, bRead, ByVal 0&
bString = Left(bString, bRead)
s = s & bString
CloseHandle hWrite
End If
Loop
CloseHandle hRead
CloseHandle pi.hThread
CloseHandle pi.hProcess
MsgBox s
exitRoutine:
Exit Sub
errHandler:
Debug.Print Err.Number, Err.Description
Resume exitRoutine
End Sub
Wrong lpEnvironment As Any parameter. Add ByVal like this
retVal = CreateProcess(vbNullString, command, pra, tra, 1, 0&, ByVal 0&, vbNullString, si, pi)

Resources