HERBERS Excel-Forum - das Archiv

Thema: Shellkommando ausführen, stderr auslesen

Shellkommando ausführen, stderr auslesen
MaxP
Hallo Alle,

ich verwende seit Jahren ein Makro, um ein Programm in einer DOSBox zu starten und die Ausgabe nach stderr auszulesen. Dazu verwende ich die Windows-API. Das Ergebnis verarbeite ich dann weiter und besetze damit bestimmte Zellen in meiner Tabelle. Hat mehr als ein Jahrzehnt funktioniert, nur musste ich das jetzt nach Excel365 portieren, und das möchte im 64bit-Adressraum laufen.
Nachdem ich dann sämtliche Änderungen der Deklarationen etc. (hoffentlich) hinbekommen hatte, läuft das Ding jetzt wieder durch. Problem: ich kann zwar stdout auslesen, aber nicht stderr.
Ich umgehe das, indem ich beim Programmaufruf stderr nach stdout umleite ("cmd.exe meinprogramm 2>&1"), aber ich wüsste wirklich gerne, was ich falsch mache. Hat jemand eine Idee?

Code:


Option Explicit

Private Declare PtrSafe Function CreatePipe Lib "kernel32" ( _
phReadPipe As LongPtr, _
phWritePipe As LongPtr, _
lpPipeAttributes As Any, _
ByVal nSize As Long) As Long

Private Declare PtrSafe Function ReadFile Lib "kernel32" ( _
ByVal hFile As LongPtr, _
ByVal lpBuffer As String, _
ByVal nNumberOfBytesToRead As Long, _
lpNumberOfBytesRead As Long, _
ByVal lpOverlapped As Any) As Long

Private Declare PtrSafe Function PeekNamedPipe Lib "kernel32" ( _
ByVal hNamedPipe As LongPtr, _
lpBuffer As Any, _
ByVal nBufferSize As Long, _
lpBytesRead As Long, _
lpTotalBytesAvail As Long, _
lpBytesLeftThisMessage As Long _
) As Long

Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As LongPtr
bInheritHandle As Long
End Type

Private Type STARTUPINFO
cb As Long
lpReserved As LongPtr
lpDesktop As LongPtr
lpTitle As LongPtr
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 LongPtr
hStdInput As LongPtr
hStdOutput As LongPtr
hStdError As LongPtr
End Type

Private Type PROCESS_INFORMATION
hProcess As LongPtr
hThread As LongPtr
dwProcessID As Long
dwThreadID As Long
End Type

Private Declare PtrSafe Function CreateProcessA Lib "kernel32" (ByVal _
lpApplicationName As Long, ByVal lpCommandLine As String, _
lpProcessAttributes As Any, lpThreadAttributes As Any, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As Long, _
lpStartupInfo As Any, lpProcessInformation As Any) As Long

Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal _
hObject As LongPtr) As Long


Private Declare PtrSafe Function GetExitCodeProcess Lib _
"kernel32" (ByVal hProcess As LongPtr, lpExitCode _
As Long) As Long

Private Const SW_SHOWNORMAL = 1
Private Const SW_HIDE = 0

Private Const STILL_ACTIVE = 259

Private Const NORMAL_PRIORITY_CLASS = &H20&

Private Const STARTF_USESHOWWINDOW = &H1&
Private Const STARTF_USESTDHANDLES = &H100&
Public Function ExecCmd(cmdline$) As String

Dim proc As PROCESS_INFORMATION, ret As Long, bSuccess As Long
Dim start As STARTUPINFO
Dim sa As SECURITY_ATTRIBUTES, hReadPipe As LongPtr, hWritePipe _
As LongPtr, hReadPipe2 As LongPtr, hWritePipe2 As LongPtr, ExitCode As Long, _
tBytesr As Long, tBytesa As Long, tMsg As Long, Result As Long

Dim bytesread As Long, mybuff As String
Dim i As Integer

mybuff = String(1024, "A")
sa.nLength = Len(sa)
sa.bInheritHandle = 1&
sa.lpSecurityDescriptor = 0&

ret = CreatePipe(hReadPipe, hWritePipe, sa, 0)
If ret = 0 Then
ExecCmd = "Error CreatePipe 1: " & Err.LastDllError
Exit Function
End If
start.hStdOutput = hWritePipe

ret = CreatePipe(hReadPipe2, hWritePipe2, sa, 0)
If ret = 0 Then
ExecCmd = "Error CreatePipe 2: " & Err.LastDllError
Exit Function
End If
start.hStdError = hWritePipe2

start.cb = Len(start)
start.dwFlags = STARTF_USESTDHANDLES Or STARTF_USESHOWWINDOW
start.wShowWindow = SW_SHOWNORMAL


ret& = CreateProcessA(0&, cmdline$, sa, sa, 1&, _
NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
If ret <> 1 Then
ExecCmd = "Error CreateProcessA: " & Err.LastDllError
Exit Function
End If
Do
GetExitCodeProcess proc.hProcess, ExitCode

'**This call returns 0
'Result = PeekNamedPipe(hReadPipe2, ByVal 0&, 0, ByVal 0&, tBytesa, ByVal 0&)

'**This call works as expected
Result = PeekNamedPipe(hReadPipe, ByVal 0&, 0&, ByVal 0&, tBytesa, ByVal 0&)

If Result <> 0 And tBytesa > 0 Then
bSuccess = ReadFile(hReadPipe, mybuff, 1024, bytesread, 0&)
If bSuccess = 1 Then
ExecCmd = ExecCmd & Left(mybuff, bytesread)
End If
End If
DoEvents
'Don't quit looping until the app has closed
Loop While ExitCode = STILL_ACTIVE

ret& = CloseHandle(proc.hProcess)
ret& = CloseHandle(proc.hThread)
ret& = CloseHandle(hReadPipe)
ret& = CloseHandle(hWritePipe)
ret& = CloseHandle(hReadPipe2)
ret& = CloseHandle(hWritePipe2)
End Function

Sub test()
Dim result As String

'**This call returns nothing
'result = ExecCmd("cmd.exe /c dir X*")

'**This call returns the expected error
result = ExecCmd("cmd.exe /c dir X* 2>&1")
MsgBox (result)
End Sub

cp vba-forum owt
ralf_b
AW: cp vba-forum owt
MaxP
Hallo,

vielen Dank, kannte ich schon. Der Code liest stdout aus - tut meiner auch. Wenn man die pipe statt an h.StdOutput an start.hStdError übergibt, funktioniert er ebenfalls nicht.
AW: cp vba-forum owt
MaxP
Manchmal hilft es, wenn man ein Problem so zu formulieren versucht, dass man es anderen erklären kann.
Hab's rausgekriegt. Die Zuweisungen

sa.nLength = Len(sa)
start.nLength = Len(start)

sind unter 64bit falsch. Korrekt ist dagegen

sa.nLength = LenB(sa)
start.nLength = LenB(sa)

Fällt nur auf, wenn man auf das letzte Element des Typs STARTUPINFO zuzugreifen versucht - blöderweise ist das genau hStdError.
AW: cp vba-forum owt
volti
Hi Max,

so ganz folgen kann ich Dir hier ja nicht. Wo ist das Problem?

Bei mir sind alle Handle gefüllt, auch die, die Du ausgeremt hast. Die results für PeekNamedPipe sind sowohl für hReadPipe1 = 1 wie auch für hReadPipe2 = 1, also m.E. erfolgreich.

Das Endergebnis result = ExecCmd("cmd.exe /c dir X*") wirft mir einen schönen Ergebnisstring aus, genauso wie der andere Aufruf..

Wozu brauchst Du eigentlich die stderr . Für mich würde es so reichen.
BTW: Ggf. noch die Umlaute umsetzen.
Option Explicit


Private Declare PtrSafe Function CreatePipe Lib "kernel32" ( _
phReadPipe As LongPtr, phWritePipe As LongPtr, _
lpPipeAttributes As Any, ByVal nSize As Long) As Long
Private Declare PtrSafe Function ReadFile Lib "kernel32" ( _
ByVal hFile As LongPtr, ByVal lpBuffer As String, _
ByVal nNumberOfBytesToRead As Long, _
lpNumberOfBytesRead As Long, ByVal lpOverlapped As Any) As Long

Private Declare PtrSafe Function PeekNamedPipe Lib "kernel32" ( _
ByVal hNamedPipe As LongPtr, _
lpBuffer As Any, ByVal nBufferSize As Long, _
lpBytesRead As Long, lpTotalBytesAvail As Long, _
lpBytesLeftThisMessage As Long) As Long

Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As LongPtr
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 LongPtr
hStdInput As LongPtr
hStdOutput As LongPtr
hStdError As LongPtr
End Type

Private Type PROCESS_INFORMATION
hProcess As LongPtr
hThread As LongPtr
dwProcessID As Long
dwThreadID As Long
End Type

Private Declare PtrSafe Function CreateProcessA Lib "kernel32" (ByVal _
lpApplicationName As Long, ByVal lpCommandLine As String, _
lpProcessAttributes As Any, lpThreadAttributes As Any, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As Long, _
lpStartupInfo As Any, lpProcessInformation As Any) As Long
Private Declare PtrSafe Function CloseHandle Lib "kernel32" ( _
ByVal hObject As LongPtr) As Long

Private Declare PtrSafe Function GetExitCodeProcess Lib "kernel32" ( _
ByVal hProcess As LongPtr, lpExitCode As Long) As Long

Private Const NORMAL_PRIORITY_CLASS As Long = &H20
Private Const STARTF_USESHOWWINDOW As Long = &H1
Private Const STARTF_USESTDHANDLES As Long = &H100


Public Function ExecCmd(cmdline$) As String
Dim hReadPipe As LongPtr, hWritePipe As LongPtr
Dim proc As PROCESS_INFORMATION, start As STARTUPINFO
Dim sa As SECURITY_ATTRIBUTES
Dim ExitCode As Long, tBytesa As Long, tMsg As Long, result As Long
Dim bytesread As Long, mybuff As String * 1024

sa.nLength = LenB(sa)
sa.bInheritHandle = 1&
sa.lpSecurityDescriptor = 0&

result = CreatePipe(hReadPipe, hWritePipe, sa, 0)
If result = 0 Then
ExecCmd = "Error CreatePipe 1: " & Err.LastDllError
Exit Function
End If
With start
.hStdOutput = hWritePipe
.cb = LenB(start)
.dwFlags = STARTF_USESTDHANDLES Or STARTF_USESHOWWINDOW
.wShowWindow = 1 ' 1 = SW_SHOWNORMAL, 0 = SW_HIDE
End With

result = CreateProcessA(0&, cmdline$, sa, sa, 1&, _
NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
If result <> 1 Then
ExecCmd = "Error CreateProcessA: " & Err.LastDllError
Exit Function
End If
Do
GetExitCodeProcess proc.hProcess, ExitCode
result = PeekNamedPipe(hReadPipe, ByVal 0&, 0&, ByVal 0&, tBytesa, ByVal 0&)
If result <> 0 And tBytesa > 0 Then
If ReadFile(hReadPipe, mybuff, 1024, bytesread, 0&) = 1 Then
ExecCmd = ExecCmd & Left(mybuff, bytesread)
End If
End If
DoEvents
Loop While ExitCode = 259 ' 259 = STILL_ACTIVE

CloseHandle proc.hProcess
CloseHandle proc.hThread
CloseHandle hReadPipe
CloseHandle hWritePipe
End Function

Sub test()
MsgBox (ExecCmd("cmd.exe /c dir *"))
End Sub


Gruß
KH
Frage zu owt
ralf_b
AW: Frage zu owt
MaxP
Abschließend der korrigierte Code, der ohne Tricksereien den Zugriff auf stderr ermöglicht (wahlweise stdout, pipe ist deklariert und erzeugt, wird im Code aber nicht ausgelesen).


Option Explicit

Private Declare PtrSafe Function CreatePipe Lib "kernel32" ( _
phReadPipe As LongPtr, _
phWritePipe As LongPtr, _
lpPipeAttributes As Any, _
ByVal nSize As Long) As Long

Private Declare PtrSafe Function ReadFile Lib "kernel32" ( _
ByVal hFile As LongPtr, _
ByVal lpBuffer As String, _
ByVal nNumberOfBytesToRead As Long, _
lpNumberOfBytesRead As Long, _
ByVal lpOverlapped As Any) As Long

Private Declare PtrSafe Function PeekNamedPipe Lib "kernel32" ( _
ByVal hNamedPipe As LongPtr, _
lpBuffer As Any, _
ByVal nBufferSize As Long, _
lpBytesRead As Long, _
lpTotalBytesAvail As Long, _
lpBytesLeftThisMessage As Long _
) As Long

Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As LongPtr
bInheritHandle As Long
End Type

Private Type STARTUPINFO
cb As Long
lpReserved As LongPtr
lpDesktop As LongPtr
lpTitle As LongPtr
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 LongPtr
hStdInput As LongPtr
hStdOutput As LongPtr
hStdError As LongPtr
End Type

Private Type PROCESS_INFORMATION
hProcess As LongPtr
hThread As LongPtr
dwProcessID As Long
dwThreadID As Long
End Type

Private Declare PtrSafe Function CreateProcessA Lib "kernel32" (ByVal _
lpApplicationName As Long, ByVal lpCommandLine As String, _
lpProcessAttributes As Any, lpThreadAttributes As Any, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As Long, _
lpStartupInfo As Any, lpProcessInformation As Any) As Long

Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal _
hObject As LongPtr) As Long


Private Declare PtrSafe Function GetExitCodeProcess Lib _
"kernel32" (ByVal hProcess As LongPtr, lpExitCode _
As Long) As Long

Private Const SW_SHOWNORMAL = 1
Private Const SW_HIDE = 0

Private Const STILL_ACTIVE = 259

Private Const NORMAL_PRIORITY_CLASS = &H20&

Private Const STARTF_USESHOWWINDOW = &H1&
Private Const STARTF_USESTDHANDLES = &H100&
Public Function ExecCmd(cmdline$) As String

Dim proc As PROCESS_INFORMATION, ret As Long, bSuccess As Long
Dim start As STARTUPINFO
Dim sa As SECURITY_ATTRIBUTES, hReadPipe As LongPtr, hWritePipe _
As LongPtr, hReadPipe2 As LongPtr, hWritePipe2 As LongPtr, ExitCode As Long, _
tBytesa As Long, Result As Long

Dim bytesread As Long, mybuff As String

mybuff = String(1024, "A")
sa.nLength = LenB(sa)
sa.bInheritHandle = 1&
sa.lpSecurityDescriptor = 0&

ret = CreatePipe(hReadPipe, hWritePipe, sa, 0)
If ret = 0 Then
ExecCmd = "Error CreatePipe 1: " & Err.LastDllError
Exit Function
End If
start.hStdOutput = hWritePipe

ret = CreatePipe(hReadPipe2, hWritePipe2, sa, 0)
If ret = 0 Then
ExecCmd = "Error CreatePipe 2: " & Err.LastDllError
Exit Function
End If
start.hStdError = hWritePipe2

start.cb = LenB(start)
start.dwFlags = STARTF_USESTDHANDLES Or STARTF_USESHOWWINDOW
start.wShowWindow = SW_SHOWNORMAL


ret& = CreateProcessA(0&, cmdline$, sa, sa, 1&, _
NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
If ret <> 1 Then
ExecCmd = "Error CreateProcessA: " & Err.LastDllError
Exit Function
End If
Do
GetExitCodeProcess proc.hProcess, ExitCode
Result = PeekNamedPipe(hReadPipe2, ByVal 0&, 0, ByVal 0&, tBytesa, ByVal 0&)
If Result <> 0 And tBytesa > 0 Then
bSuccess = ReadFile(hReadPipe2, mybuff, 1024, bytesread, 0&)
If bSuccess = 1 Then
ExecCmd = ExecCmd & Left(mybuff, bytesread)
End If
End If
DoEvents
'Don't quit looping until the app has closed
Loop While ExitCode = STILL_ACTIVE

ret& = CloseHandle(proc.hProcess)
ret& = CloseHandle(proc.hThread)
ret& = CloseHandle(hReadPipe)
ret& = CloseHandle(hWritePipe)
ret& = CloseHandle(hReadPipe2)
ret& = CloseHandle(hWritePipe2)
End Function

Sub test()
Dim result As String
result = ExecCmd("cmd.exe /c dir X*")
MsgBox (result)
End Sub
AW: Frage zu owt
volti
Hallo zusammen,

ist ja nun gelöst, wie ich jetzt erst gesehen habe.

Unter 64 Bit ist grundsätzlich für die TYPE-Größenangabe LenB() zu verwenden. Ohne das hat schon so manches andere nicht funktioniert. Leider meistens, wie schon angeführt, ohne Fehlerausgabe.

Gruß KH
AW: Frage zu owt
MaxP
https://www.herber.de/forum/archiv/1764to1768/1764129_Fehler_CreatePipe_in_Excel_64bit.html#4

Da hast Du recht ;-)
Sonst habe ich eine Menge aus Deinem Beitrag von damals gelernt, war für meinen ersten Kontakt mit 64bit-VBA sehr hilfreich. Dafür nochmal ganz herzlichen Dank!

LG

Max
AW: Frage zu owt
volti
Gerne, freut mich zu hören 😎

Gruß KH