MsgWaitForMultipleObjectsEx returns 87 in VB6 - winapi

i am currently writing a service in VB6 for a real legacy application.
I am basing my work on Sergey Merzlikin "NT service sample" (http://www.smsoft.ru/en/ntservice.htm).
Until today I used NTSVC.OCX but it seems it does not work on Windows Server core because it involves a form in an ActiveX (i suppose).
So i jumped to full Winapi service and mainly i have a problem on :
MsgWaitforMultipleObjectsEx, it returns 87 (invalid parameter)
This API seems extremely tricky.
The original code uses MsgWaitForMultipleObjects but it resulted in an app crash (c0000005) in MSVBVM60.dll.
Testing platform : Windows 2012
I tweaked quite a bit the declarations, so maybe i made something wrong.
Anyway, here are the most relevant parts of the code :
Private Declare Function MsgWaitForMultipleObjectsEx Lib "user32" _
(ByVal nCount As Long, pHandles As Long, _
ByVal dwMilliseconds, _
ByVal dwWakeMask As Long, _
ByVal dwFlags As Long) As Long
Public Declare Function CreateEvent2 Lib "kernel32" Alias "CreateEventW"
(ByVal lpEventAttributes As Long, _
ByVal bManualReset As Long, _
ByVal bInitialState As Long, _
ByVal lpName As String) As Long
hStopEvent = CreateEvent2(0&, 1&, 0&, vbNullString)
hStopPendingEvent = CreateEvent2(0&, 1&, 0&, vbNullString)
hStartEvent = CreateEvent2(0&, 1&, 0&, vbNullString)
ServiceNamePtr = StrPtr(Service_Name)
I checked the handles values, they seem OK, but i suspect that they have no security descriptor with "SYNCHRONIZE"
http://msdn.microsoft.com/en-us/library/windows/desktop/aa379607%28v=vs.85%29.aspx
then there is a wrapper called MsgWaitObj in the sample NT Service. this is where I replaced MsgWaitForMultipleObjects with :
MsgWaitForMultipleObjectsEx(nObj, hObj, T1, QS_ALLEVENTS, 0&)
The wrapper function declaration is :
Public Function MsgWaitObj(ByVal Interval As Long, _
Optional ByRef hObj As Long, _
Optional ByVal nObj As Long = 0&) As Long
The external declaration of MsgWaitObj in Main and the references passed are as follows :
hnd = NTService.StartAsService
h(0) = hnd
h(1) = hStartEvent
j(0) = hStopPendingEvent
<...>
IsNTService = MsgWaitObj(INFINITE, h(0), 2&)
<...>
Do
tm = MsgWaitObj(5000&, j(0), 1&)
Loop While tm = WAIT_TIMEOUT
I hope posting excerpts of code is sufficient, if not i think the easiest way would be to download the ntservice sample and figure what is going wrong and if i am in the right path or not.
thanks for your help.

I tested Sergey Merzlikin's sample application from "Writing NT service using VB6/VB5" article refrenced in the question, and observed quite different behavior. Details are below, but my guess is that Server Core installations do NOT support VB6 applications.
Windows 2012 with full GUI
Sample Windows service SvSample.exe works just fine on fresh installation of Windows 2012 with full GUI with no configuration changes applied.
Windows 2012 with Minimal Server Interface
Then I go from full-blown GUI to minimal interface via this PowerShell command: Uninstall-WindowsFeature -Name Server-Gui-Shell –Restart. After that sample service still does work without obvious problems.
Windows 2012 Server Core
Then I strip down remaining bits with Uninstall-WindowsFeature -Name Server-Gui-Mgmt-Infra –Restart. After configuration is done and server reboots, I double check that ServerCore-WOW64 feature is still installed (without it no 32-bit application would work as I understand).
At this point sample service indeed stops working. Though not with crashes in msvbvm60.dll, rather Service Control Manager bluntly reports that (as seen in System event log):
A timeout was reached (30000 milliseconds) while waiting for the
Sample VB6 Service service to connect.
The Sample VB6 Service service failed to start due to the following error:
The service did not respond to the start or control request in a timely fashion.
In fact, msvbvm60.dll is not even present in C:\Windows\SysWOW64\.
Conclusion
Server Core (as opposed to Windows 2012 in general) simply does not support VB6 runtime. I didn't check, but I'm pretty sure that situation is the same with Windows 2008 R2 Server Core too.

Related

Access any software through excel or Scripting or Task?

I am using software which actually send a message to LED message display but It needs to press send button every time.Is there anyway to access that button or make any task which actually press that button after some seconds.Because I want to send the message continuously after 2 seconds.
I realize this is not a full answer to your question, I do not have sufficient reputation to add a comment.
If the LED message display software that you are using does not have a published interface to allow you to control it then you may need to use API calls. You can declare a function which makes a call directly to the API library. A useful call is FindWindowExA. This finds information about the windows open on the computer. All the windows are in a hierarchy. You can then find the application window for the LED message software and step through the sub windows until you find the one that represents the send button. You should be able to active its press event.
API calls are declared like this:
Private Declare Function local_name Lib "user32" Alias "library_routine_name" (....) As long
where local_name is the name of the function in your code and library_routine_name is the name of the routine that you wish to call
for example:
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, _
ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
declares a function called FindWindowEx which calls the library routine FindWindowExA. The arguments to the call are specified by the routine that you are calling.
MSDN provides descriptions of the API calls, for example see
FindWindow help
FindWindowExA help

VB6 - How do I open a file from an app running as a service

I have been struggling with this problem for almost a week now. I need a vb6 application which is running as a service to open a file. I don't need to do anything with the file, I just need it to open. I have tried using ShellExecute and ShellExecuteEx as well as using CreateProcess to attempt to launch the file from the command line. When none of these implementations worked, I tried instead launching another application (using CreateProcess) with the sole task of opening the file and then closing itself.
These solutions all work when the application is run normally, but Not when it is run as a service. It is extremely important that the application be able to open the file while running as a service, either directly or indirectly, it just needs to be able to trigger it.
I understand that Windows has locked down the ability of services to interact with the desktop since Windows Vista, but I'm sure there must be a way to trigger a file open command from a service. The app I've developed is able to run pg_dump.exe (a backup executable for postgres databases) from the command line with CreateProcess to backup database files, while running as a service. That is why I though launching an exe from the service to indirectly open the file might work. However, for some reason the application runs pg_dump.exe fine but will not run the executable I created. I'm wondering if the exe I created is expecting to have some sort of presence on the desktop and that is why the service doesn't want to start it. I changed the properties of the main form in the secondary exe so that the form would not be visible and wouldn't show up on the taskbar, but something tells me that's not enough.
Here is my CreateProcess code (I didn't write most of this so please excuse my ignorance):
Private Declare Function WaitForSingleObject Lib "KERNEL32" (ByVal _
hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare Function CloseHandle Lib "KERNEL32" _
(ByVal hObject As Long) As Long
Private Declare Function GetExitCodeProcess Lib "KERNEL32" _
(ByVal hProcess As Long, lpExitCode As Long) As Long
'create a new win process.
Private Declare Function CreateProcessA Lib "KERNEL32" (ByVal _
lpApplicationName 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
'used by CreateProcess
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
Const NORMAL_PRIORITY_CLASS = &H20&
Const INFINITE = -1&
Public Function ExecSynchronousCmd(cmdline As String) As Long
' - Used to force a shelled command to run synchronously (code will
' suspend where this function is called until shelled process
' returns a return value)
' - There is no time out - it will wait forever!!
' - Function returns exit value for shelled process
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
Dim ret As Long
'Initialize the STARTUPINFO structure:
start.cb = Len(start)
'Start the shelled application:
ret = CreateProcessA(vbNullString, cmdline$, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, vbNullString, start, proc)
'Wait for the shelled application to finish:
ret = WaitForSingleObject(proc.hProcess, INFINITE)
Call GetExitCodeProcess(proc.hProcess, ret&)
Call CloseHandle(proc.hThread)
Call CloseHandle(proc.hProcess)
ExecSynchronousCmd = ret
End Function
Here is the implementation for running pg_dump.exe which is SUCCESSFUL at running the exe From the service and creating database backup files:
i = ExecSynchronousCmd(Chr$(34) & "C:\Program Files (x86)\PostgreSQL\9.3\bin\pg_dump.exe" & Chr$(34) & _
" -Ft " & _
" -f " & Chr$(34) & tempName & Chr$(34) & _
" -U " & s1 & _
" -h " & s3 & _
" -p " & s4 & _
" " & sDB(0, x))
Here is a similar implementation which tries to run the secondary exe that will attempt to open the file in question:
i = ExecSynchronousCmd(Chr$(34) & "C:\Program Files (x86)\GranDocsNP\GDNPOpener.exe" & Chr$(34))
The above code does not work when the app is run as a service. Why is pg_dump.exe successful in running but my own GDNPOpener.exe is not?
As I stated above, I also tried using ShellExecute and ShellExecuteEx to open the file directly from the service, which didn't work. (I am using ShellExecuteEx within the secondary exe (GDNPOpener.exe) to open the file)
If anyone knows how to fix my exe so that my service will run it, I would greatly appreciate the help! If anyone knows any alternative ways to open a file from a service, that would be appreciated as well, thanks!
You cannot simply call Shellexecute() in the context of a process running as a service. Even if it succeeds (never really tested it), the launched app/document still wouldn't show on the desktop of the logged-on user because those are two different sessions, isolated from each other (unless you're on Windows XP or Windows Server 2003; for those, services & console-session apps run within session 0 but a service still needs to be marked as interactive to interact with the desktop).
See http://blogs.technet.com/b/askperf/archive/2007/04/27/application-compatibility-session-0-isolation.aspx or search "Session 0 Isolation" for more details on this concept.
To address this, you have mainly two options: (**if I missed one, feel free to correct me!)
Use the client/server route;
e.g. You could write a small utility that will run in user space (for example, launched on session start) and communicate with your service over some choice of inter-process communication (IPC; named pipes, mapped memory, etc.). Your service could then ask the utility to open files in the context of the logged-on user (so it would be the utility that calls ShellExecute(), for example).
To know where to start, look for examples of inter-process communications written for VB6.
IMPORTANT: Since the server (your service) and the client (the user-space utility) will communicate across sessions and under different user tokens, pay attention to notes on security descriptors used and access rights required when creating and accessing communication channels (named pipe, etc.), so as not to have your client get "access denied" errors.
WARNING: The usual security warnings regarding the risk of privilege escalation apply here. Since you basically "open a door" into your service, take extra care not to allow any rogue application posing as your client/utility to take control of your service (buffer overrun, etc.) or make it do something it shouldn't do.
Make use of dedicated APIs for starting processes across different sessions;
Depending on (1) whether your service runs under the LocalSystem account or another, administrative account; (2) on whether you want to launch the document/app in the context of the logged-on user or you don't care creating a new session, and (3) whether you have the user's credentials (user+password) or not, a few APIs exist to allow a service to communicate with the desktop or launch an app in the context of another user.
Have a look at the APIs CreateProcessAsUser(), CreateProcessWithLogonW(), WTSQueryUserToken() and related ones, or search for examples using them. Also, the following article could be a good read: Launching an interactive process from Windows Service in Windows Vista and later
Hope this answer your question! At least, it should give you pointers on what to do next.

Is it possible to write a App.PrevInstance Replacement to give real time information

App.Previnstance returns a value of True or False depending on whether a previous of the program is running when this instance starts.
Subsequently if the previous instance is terminated the value of App.PrevInstance does not change.
Is it possible to write a function that would be able to determine at any moment if previous
instances are in existence?
I guess that for this you would need the date/time processes started to be available.
As this information does not seem to be available from the task manager I wonder if windows stores it at all?
The problem you're seeing with App.PrevInstance might be because you're testing with the application running under the debugger (i.e., the VB 6 IDE). But I'm not entirely sure. It might just perform the check once and cache the value, which allows it to grow "stale" as the environment state changes. As you've already noticed, the App.PrevInstance property has a lot of limitations. Another common problem with it is its fragility. Changing the name of the executable is an easy way to make it fail. That is not always the desired behavior.
So it's a good idea to replace it with an alternative solution. Like wqw says in a comment, the best solution would be to create a mutex using a GUID for the name of the mutex whenever your application starts up. This should succeed the first time, but will fail subsequently because the mutex is already registered (by a previous instance of your application). That failure is your clue that a previous instance of your application is running. To do this in VB 6, you will need to import and call some Win32 functions, like CreateMutex, CloseHandle, and ReleaseMutex. There's a sample of how to use mutexes on MSDN, but that won't help you very much to write VB 6 code unless you are already rather familiar with the Win32 API. I've linked to a tutorial that contains the necessary code in VB 6 in my answer here.
If you're otherwise satisfied with the behavior of App.PrevInstance and you just want it to perform the check each time you call it (rather than using a stale cached value), then you can just replace it with a call to your own function that does essentially the same thing: iterate through all of the currently-running processes, and look for a match to the name of your executable. Unfortunately, this is not necessarily less work than the "better" solution involving the use of a mutex. You'll still need to import a number of Win32 functions, including EnumProcesses. There are instructions for this in an old knowledge base article—obviously you want to focus on the "Windows NT" section and ignore the "Windows 95/98" stuff.
I guess that for this you would need the date/time processes started to be available. As this information does not seem to be available from the task manager I wonder if windows stores it at all?
You don't actually need this information. In fact, I'm not sure what approach you had in mind that would require it. It doesn't matter when the process was started, it just matters whether or not it is currently running. Those are two completely different things.
However, just for fun, Windows does in fact store this information. Task Manager doesn't show it, but Process Explorer does. You can retrieve it programmatically either by calling the GetProcessTimes function, or querying WMI (specifically, the CreationDate property of the Win32_Process class).
Thanks to wqw's comment I looked up CreateMutex and it was exactly what I needed.
I found the code below here
'Code by Adam Verwijs
Const ERROR_ALREADY_EXISTS = 183&
Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lpMutexAttributes As Any, ByVal bInitialOwner As Long, ByVal lpName As String) As Long
Private Declare Function ReleaseMutex Lib "kernel32" (ByVal hMutex As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Sub Form_Load()
Dim hMutex As Long
'Try to create a new Mutex
hMutex = CreateMutex(ByVal 0&, 1, App.Title)
'Did the mutex already exist?
If (Err.LastDllError = ERROR_ALREADY_EXISTS) Then
'Clean up
ReleaseMutex hMutex
CloseHandle hMutex
'More than one instance detected
MsgBox "More than one instance"
End
Else
'form load code
End If
End Sub
EDIT to show that same non zero mutex returned :
If you create a new vb6 project with 1 button, stick the code below in, make the project and then run multiple instances you'll see that all have the same non-zero mutex, at least on my computer (windows vista home basic)
Option Explicit
Const ERROR_ALREADY_EXISTS = 183&
Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lpMutexAttributes As Any, ByVal bInitialOwner As Long, ByVal lpName As String) As Long
Private Declare Function ReleaseMutex Lib "kernel32" (ByVal hMutex As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Sub Command1_Click()
Dim hMutex As Long
'Try to create a new Mutex
hMutex = CreateMutex(ByVal 0&, 1, App.Title)
MsgBox hMutex
'Did the mutex already exist?
If (Err.LastDllError = ERROR_ALREADY_EXISTS) Then
'Clean up
ReleaseMutex hMutex
CloseHandle hMutex
'More than one instance detected
MsgBox "More than one instance"
End If
End Sub
EDIT 2016-04-17 DO NOT USE THIS CODE!!!
I have used it without noticing a problem until recently but have now discovered that it does not work across more than one user logged in on a computer. Use wqw's answer on this other thread instead

How to rerun my program in vb6?

How to rerun (close then run) my program in vb6
The best way to do this is probably to create a "watcher" application that you launch in your main app before terminating it.
First, the main app should create a mutex or semaphore upon launch. When you want to do the app restart, have the main app launch the watcher. The watcher application should wait until the main application mutex/semaphore is gone, then re-launch the main application.
I recently released a semaphore class written in VB6 which would take care of some of the hard work: http://www.vbforums.com/showthread.php?t=634635
Stop the currently running executable( click the button with a square block image on it on the IDE ) and then shift+F5, to restart the program.
Another approach might be to create a batch file that starts the executable. Call this with an asynchronous call.
You will then need a littlembit of logic to check that you don't have two instances running at once. If there., the new one can wait a second or so. If it is still there, a user warning can be given.
Try this.
Main Project :
Private Sub Command2_Click()
Call Shell(App.Path & "\Restart.exe", 1)
End Sub
Restart project :
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Const WM_CLOSE = &H10
Private Sub Form_Load()
Dim WinWnd As Long
WinWnd = FindWindow(vbNullString, "Form1") 'Replace Form1 with Whatever your Form's Caption is
If WinWnd <> 0 Then
PostMessage WinWnd, WM_CLOSE, 0&, 0&
Else
MsgBox "No window of that name exists."
End If
Call Shell("Main.exe", 1)
End Sub
found here http://www.vbforums.com/showthread.php?600511-RESOLVED-Application-restarting-itself

How do I wake up my Windows monitors once turned off by power settings?

So my Vista machine is set to turn off it's monitors after 10 minutes. (Note : machine is not suspended, or hibernated)
I have a .Net application running, that needs to "wake" the system at a specified time. As a human, you just move the mouse or press a key. How do I do this programatically?
I've tried : programatically setting the cursor; using "SendKeys"; and even pinvoking CreateWaitableTimer to unsuspend (even though it's not suspended) in the hope that that would trigger something.
Ideally code in c# would be great, but the correct Win API would suffice.
Many thanks in advance.
You should be able to control monitor power by sending system command messages as follows. Note that this is tested on XP, Vista may have changed things somewhat so you'll need to test it and let us know.
This code is in VB but you can see the Win32 API call that it uses. You need to pass a window handle to the function so your code will need a window created to process the message (just pass it through to the default window processing function).
Const SC_MONITORPOWER As Integer = &HF170
Const WM_SYSCOMMAND As Short = &H112S
Private Function SendMessage(
ByVal Handle As Int32,
ByVal wMsg As Int32,
ByVal wParam As Int32,
ByVal lParam As Int32) As Int32
End Function
Sub MonStandBy(hWnd as Int32)
SendMessage(hWnd, WM_SYSCOMMAND, SC_MONITORPOWER, 1)
End Sub
Sub MonOff(hWnd as Int32)
SendMessage(hWnd, WM_SYSCOMMAND, SC_MONITORPOWER, 2)
End Sub
Sub MonOn(hWnd as Int32)
SendMessage(hWnd, WM_SYSCOMMAND, SC_MONITORPOWER, -1)
End Sub

Resources