the problem is: Getting a process ID from a process handle (which I
receive, in turn, from ShellExecuteEx).
I've solved the problem, using NtQueryInformationProcess, but
unfortunately, this doesn't work in Windows Server 2008 (32 bit)
After reading up in MSDN, I decided to replace
NtQueryInformationProcess with GetProcessID, an API call seemingly
introduced with XP SP1.
The call seems straightforward - HANDLE in, DWORD out. I made the
following declare:
Private Declare Function GetProcessId Lib "kernel32" (ByVal hProc As
Long) As Long
It works perfectly under XP (just as NtQueryInformationProcess did),
but on the Server 2008, it returns any random number - not zero, but
not the process I am looking for (and usually not even a valid
process).
Does anyone know about these issues and what to do about it? Thanks!
Robert
No. NtQueryInformationProcess really works in Windows Server 2008
32-bit. Anyway, it is preferable to use GetProcessId instead, as you already
noticed.
> The call seems straightforward - HANDLE in, DWORD out. I made the
> following declare:
>
> Private Declare Function GetProcessId Lib "kernel32" (ByVal hProc As
> Long) As Long
>
> It works perfectly under XP (just as NtQueryInformationProcess did),
> but on the Server 2008, it returns any random number - not zero, but
> not the process I am looking for (and usually not even a valid
> process).
>
> Does anyone know about these issues and what to do about it? Thanks!
Under Windows Server 2008 (and also XP/Vista), the GetProcessId function
works fine in my side, after a success call to ShellExecuteEx I always get a
valid handle for that process, and GetProcessId returns its actual
identifier. If the call fails, hProcess is 0, so I can't reproduce the
behaviour you describe.
Show us how are you calling ShellExecuteEx, as well as your declare for
SHELLEXECUTEINFO structure, so we can try to see what is going on, .
Alternatively, if you finally can't be able to solve your problem with
getting the process identifier of the shelled application using
GetProcessId, note that actually you can get it through Process32Next,
looping a processes snapshot until you find the latest process that its
'.th32ParentProcessID' of a PROCESSENTRY32 structure is your
GetCurrentProcessId, as follows:
'***************
Function GetLastShelledProcessId(ByVal ParentProcessID As Long) As Long
Dim hSnapshot As Long
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
If hSnapshot <> 0 Then
Dim pe32 As PROCESSENTRY32: pe32.dwSize = Len(pe32)
Do Until Process32Next(hSnapshot, pe32) = 0
If ParentProcessID = pe32.th32ParentProcessID Then _
GetLastShelledProcessId = pe32.th32ProcessID
Loop
CloseHandle hSnapshot
End If
End Function
'***************
If ShellExecuteEx(SEI) <> 0 Then
ShelledAppProcessId = GetLastShelledProcessId(GetCurrentProcessId)
...
..
--
Regards
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
( ! ) http://groups.google.com/group/microsoft.public.vb.winapi
( i ) http://www.microsoft.com/communities/conduct/default.mspx
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
' In Form1:
Option Explicit
Private Sub Command1_Click()
Dim ProcessInfo As PROCESS_INFORMATION
StartProcess "Notepad", ProcessInfo
Debug.Print "Process ID: " & ProcessInfo.dwProcessId
Debug.Print "Process handle: " & ProcessInfo.hProcess
ReleaseProcessData ProcessInfo
End Sub
' In Module1:
Option Explicit
Public Const NORMAL_PRIORITY_CLASS As Long = &H20
Public 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
Public Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadID As Long
End Type
Public Declare Function CreateProcess Lib "kernel32" Alias _
"CreateProcessA" (ByVal lpAppName As String, _
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 String, lpStartupInfo As STARTUPINFO, _
lpProcessInformation As PROCESS_INFORMATION) As Long
' Returns 0 when successful, process ID and handle are in ProcessInfo
Public Function StartProcess(ByRef sCommandLine As String, _
ByRef ProcessInfo As PROCESS_INFORMATION) As
Long
Dim ret As Long
Dim sWokingDir As String
Dim Start As STARTUPINFO
If Len(sCommandLine) > 0 Then
' Initialize the STARTUPINFO structure:
Start.cb = Len(Start)
sWokingDir = App.Path
' Start the process
ret = CreateProcess(vbNullString, sCommandLine, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, sWokingDir, Start, ProcessInfo)
If ret <> 0 Then
' Success
StartProcess = 0
Else
' Failed to start process
Debug.Print "StartProcess failed: LastDllError = " & _
Err.LastDllError
StartProcess = 1
End If
End If
End Function
' Close process and thread handles, must be called when no longer needed
Public Sub ReleaseProcessData(ByRef ProcessInfo As PROCESS_INFORMATION)
If ProcessInfo.hProcess <> 0 Then
CloseHandle ProcessInfo.hThread
CloseHandle ProcessInfo.hProcess
ProcessInfo.hProcess = 0
ProcessInfo.hThread = 0
End If
End Sub
Well, basicly the same as:
ProcessId = VBA.Shell("Notepad", vbNormalFocus)
But, I was thinking about why Robert is getting 'random' numbers. I
don't think that are random numbers at all, but the process id of a
currently non-existent process. This means that the call really succeeds,
and it was a valid process identifier of a process, but that process
launches another process or sends information to the process that will take
the control, as Explorer.exe do when we execute a sentence like
Shell("Explorer.exe x:\Path", nShow), so a new instance of Explorer.exe is
created and terminated after the first running Explorer.exe creates a new
thread that creates the explorer window UI with the arguments sent.
He didn't mention if the wrong results are always related to a single
executable, if it happens always or sometimes works and sometimes fails, or
randomly works and fails with any executable. Robert have to provide more
information...
Sorry, the condition must be:
If hSnapshot <> INVALID_HANDLE_VALUE Then
Pretty close!
We've got a Microsoft support call open on this, and so far I can say
that actually two processes are started, and one of them closes
quickly again. This is the process I get the ID of.
Initially they suggested it had to do with UAC, but later confirmed it
also occurred with UAC disabled.
I've since changed my code to locate the "oskmain" window, whatever
the pID is (fortunately, there can only be one, anyway). Of course, it
might not work in all situations using ShellExecuteEx. I'll have to
take a look at my code for that.
Robert
NtQueryInformationProcess works just fine for us on all OS's.
Here's a sample that I just tested on Server 2008 x64, XP x86, and Win7 x86.
Paste it into a form with a command button (command1) and run it
Option Explicit
Private Const PROCESS_BASIC_INFO = &H0
Private Const SEE_MASK_NOCLOSEPROCESS = &H40
Private Type PROCESS_BASIC_INFORMATION
ExitStatus As Long
PebBaseAddress As Long
AffinityMask As Long
BasePriority As Long
UniqueProcessId As Long
InheritedFromUniqueProcessId As Long
End Type
Private Type UNICODE_STRING
Length As Integer
MaximumLength As Integer
Buffer As Long
End Type
Private Type SHELLEXECUTEINFO
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
Private Declare Function OpenProcess Lib "Kernel32.dll" _
(ByVal dwDesiredAccessas As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcId As Long) As Long
Private Declare Function NtQueryInformationProcess Lib "ntdll.dll" _
(ByVal hProcess As Long, _
ByVal ProcessInformationClass As Long, _
ProcessInformation As Any, _
ByVal ProcessInformationLength As Long, _
ReturnLength As Long) As Long
Private Declare Function ShellExecuteEx Lib "shell32.dll" _
(SEI As SHELLEXECUTEINFO) As Long
Private Sub LaunchMe(FileName As String, OwnerhWnd As Long)
Dim SEI As SHELLEXECUTEINFO
Dim lRet As Long
With SEI
.cbSize = Len(SEI)
.fMask = SEE_MASK_NOCLOSEPROCESS
.hwnd = OwnerhWnd
.lpVerb = "open"
.lpFile = FileName
.lpParameters = vbNullChar
.lpDirectory = vbNullChar
.nShow = 1
.hInstApp = 0
.lpIDList = 0
End With
lRet = ShellExecuteEx(SEI)
If (SEI.hProcess) Then
MsgBox "hProcess returned as " & SEI.hProcess
MsgBox "The Process ID you just launched is " &
WhatsMyProcessID(SEI.hProcess)
Else
MsgBox "No hProcess returned"
End If
End Sub
Private Sub Form_Load()
LaunchdMe "c:\windows\system32\notepad.exe", Me.hwnd
End Sub
Public Function WhatsMyProcessID(hProcess As Long) As Long
Dim pbi As PROCESS_BASIC_INFORMATION
Dim Ret As Long
On Error GoTo ErrHandler
If hProcess Then
If NtQueryInformationProcess(hProcess, PROCESS_BASIC_INFO, ByVal
VarPtr(pbi), Len(pbi), Ret) = 0 Then
WhatsMyProcessID = pbi.UniqueProcessId
End If
End If
Exit Function
ErrHandler:
'shoe something decent here
MsgBox Err.Description & " " & Err.Number
End Function
Cheers,
Crispin.
"Crispin" <Cri...@discussions.microsoft.com> wrote in message
news:2DD76A47-9250-48DF...@microsoft.com...
I'm not "using it to launch a secondary process", it's just something
that Windows does with certain applications in certain Windows
versions. This has since been verified by Microsoft Support.
I launch "osk.exe" in XP, and it works fine.
I do the same in Windows 2008 Server, and a Windows layer catches my
call, and starts its own process.
I've found a workaround for my specific case - I'm waiting for a
certain window to appear (it can only exist once system-wide), and get
the process ID via hwnd.
It is interesting what happens when you try to launch the osk.exe,
things that you have to take into account for your workaround.
Supose this: your process with PID = 2636 starts osk.exe, and the
following happens:
YourProcess.exe [2636] launches osk
osk.exe [2888] starts with your PID as the ParentPID.
[2888] exits with an exit status C000042C (STATUS_ELEVATION_REQUIRED)
After [2888] exits, another "child-process" osk.exe [3644] is launched
from your YourProcess.exe.
Consent.exe created by an instance of svchost is doing its jobs, later
WinLogon.exe [544] comes to scene creating an instance of UtilMan.exe [2508]
The second started osk [3644] by your process exits with status 0.
UtilMan [2508] starts a new osk.exe [1976], wich finally exits with
STATUS_ELEVATION_REQUIRED again.
Then UtilMan [2508] creates the last osk.exe [2876] with the required
integrity level, and [2508] exits.
Therefore, it can't be relied on waiting for a main window from a thread
of osk.exe to appear, immediately after lauching that application. Moreover,
on every User-switch, the running osk.exe is teminated, and a new osk.exe
instance created after log on, and also an execution of Consent.exe by a
request from the user to start an application that requires administrative
privileges (or by 'Run As Administrator' context menu), terminates osk.exe
and re-executes osk. So, it will not be reliable to work with a handle to
its main window or its process identifier stored in a module level variable,
you must to search for it every time you need it (well, in fact we don't
know exactly what you really need to do...)
Thanks for that explanation!
I use the window handle to set style, window size and position. Our
customers using touch panels need the keyboard to automatically appear
whenever they click on a text box or similar control, and appear in a
position not to obscure the window they want to type in. And it should
be as big as possible (some of your customers must have rather big
fingers).
I wait up to 3 seconds after process creation, I hope that is
sufficient (normally, a few milliseconds are enough).
Then I do my stuff right away. I later use the window handle to hide
the keyboard (when it's no longer needed), and re-show it (prevents
flickering), so if Windows re-started the process, that would be a
problem.
However, we control the environment tightly on those touchpanels, the
user basically sees our application and that's it.
"Run as" or user change are highly unlikely while the keyboard is
displayed (besides, by the time the user opened the context menu to
select "Run as...", I'd have already closed the keyboard, because the
focus is no longer on a text box).
Robert