I need to write a little application in VB6 to run instances of another VB6 application and keep an eye on the running processes, but I don't have any idea how to get process information in VB6. I can see some of what I need with the tasklist utility but I don't really know how create processes (specifying the process or application name if possible) and fetching information about processes from the operating system.
This application is to run on a Windows XP machine.
Does anyone know of a get-you-started tutorial or helpful web page for this sort of thing?
There are numerous Windows API functions you can use to do this. I'd start with looking at EnumProcesses (VB6 example and declaration here) which can be used to gather information about all running processes. You can also use OpenProcess
to start interrogating Windows about a particular process (another VB6 example).
There is also a fairly nice example on MSDN.
And of course, there is CreateProcess (AllApi link) or ShellExecute (AllApi) for spawning processes - the former gives you more control over the creation of the process, while the latter is a much simpler call.
There was another question posted about this a while back with some example code.
Another possible approach would be to use WMI (some useful snippets to adapt).
Finally, here are some tutorials that show you how to do it (I'd recommend trying it yourself first though :):
Getting Process Information using PSAPI
Another EnumProcesses/OpenProcess implementation
WMI-based demonstration
Here are some related questions although you probably already saw them when you searched this site before posting:
Monitoring processes to see if they've crashed in vb6
How can I execute a .bat file but wait until its done running before moving on?
How To Enumerate Processes From VB 6 on Win 2003?
Since you say the other application is ** also VB6**, it would be easier to make the other application into an ActiveX exe. Then you can get references to objects in the other application direct from your first application. COM solves it all for you.
Here's Microsoft's tutorial on the subject - you can download the code too.
Or here's another answer where I've written about this
You don't need to go spelunking for processes just to get a handle to child processes that you spawn. The VB6 Shell() function returns a Process ID you can use to call OpenProcess with. CreateProcess gives you the handle directly.
Ok, here is a super-stripped-down example of a program in VB6 to spawn and monitor programs. The example is coded to start and repeatedly restart 3 copies of the command shell (trivial sample child program). It is also written to kill any running children when it is terminated, and there are better alternatives to use in most cases. See A Safer Alternative to TerminateProcess().
This demo also reports back the exit code of each process that quits. You could enter exit 1234 or somesuch to see this in action.
To create the demo open a new VB6 Project with a Form. Add a multiline TextBox Text1 and a Timer Timer1 (which is used to poll the children for completion). Paste this code into the Form:
Option Explicit
Private Const SYNCHRONIZE = &H100000
Private Const PROCESS_QUERY_INFORMATION = &H400&
Private Const PROCESS_TERMINATE = &H1&
Private Const WAIT_OBJECT_0 = 0
Private Const INVALID_HANDLE = -1
Private Const DEAD_HANDLE = -2
Private Declare Function CloseHandle Lib "kernel32" ( _
ByVal hObject As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" ( _
ByVal hProcess As Long, _
ByRef lpExitCode As Long) As Long
Private Declare Function OpenProcess Lib "kernel32" ( _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Private Declare Function TerminateProcess Lib "kernel32" ( _
ByVal hProcess As Long, _
ByVal uExitCode As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" ( _
ByVal hHandle As Long, _
ByVal dwMilliseconds As Long) As Long
Private Tasks() As String
Private Handles() As Long
Private Sub Form_Load()
Dim I As Integer
'We'll run 3 copies of the command shell as an example.
ReDim Tasks(2)
ReDim Handles(2)
For I = 0 To 2
Tasks(I) = Environ$("COMSPEC") & " /k ""#ECHO I am #" & CStr(I) & """"
Handles(I) = INVALID_HANDLE
Next
Timer1.Interval = 100
Timer1.Enabled = True
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
Dim I As Integer
Timer1.Enabled = False
DoEvents
For I = 0 To UBound(Tasks)
If Handles(I) <> INVALID_HANDLE And Handles(I) <> DEAD_HANDLE Then
TerminateProcess Handles(I), 666
CloseHandle Handles(I)
Handles(I) = DEAD_HANDLE
End If
Next
End Sub
Private Sub Timer1_Timer()
Dim I As Integer
Dim ExitCode As Long
Dim Pid As Long
Timer1.Enabled = False
For I = 0 To UBound(Tasks)
If Handles(I) <> INVALID_HANDLE Then
If WaitForSingleObject(Handles(I), 0) = WAIT_OBJECT_0 Then
If GetExitCodeProcess(Handles(I), ExitCode) <> 0 Then
Text1.SelText = "Task " & CStr(I) & " terminated, " _
& "exit code: " & CStr(ExitCode) _
& ", restarting task." _
& vbNewLine
Else
Text1.SelText = "Task " & CStr(I) & " terminated, " _
& "failed to retrieve exit code, error " _
& CStr(Err.LastDllError) _
& ", restarting task." _
& vbNewLine
End If
CloseHandle Handles(I)
Handles(I) = INVALID_HANDLE
End If
End If
If Handles(I) = INVALID_HANDLE Then
Pid = Shell(Tasks(I), vbNormalFocus)
If Pid <> 0 Then
Handles(I) = OpenProcess(SYNCHRONIZE _
Or PROCESS_QUERY_INFORMATION _
Or PROCESS_TERMINATE, 0, Pid)
If Handles(I) <> 0 Then
Text1.SelText = "Task " & CStr(I) & " started." _
& vbNewLine
Else
Text1.SelText = "Task " & CStr(I) _
& ", failed to open child process." _
& vbNewLine
Handles(I) = DEAD_HANDLE
End If
Else
Text1.SelText = "Task " & CStr(I) _
& ", failed to Shell child process." _
& vbNewLine
Handles(I) = DEAD_HANDLE
End If
End If
Next
Timer1.Enabled = True
End Sub
Hopefully this helps answer the question.
something more simple will use sockets.
Launch you app server and on your client implements the communication against your server. With that you will provide intercommunication.
well i say. because i dont be what you try do
Sorry it only apply if your clients are done in house i you have the option of added changes
Related
I've searched around for the proper way to kill a previous Instance of a VB6 application to no avail. If our main service dies, we need to terminate the previous instance of the running application and then, when the service comes back up only the new instance should be loaded. I am trying to exit this code in the following function:
Private Sub g_cServerInterface_FatalError(Error As enSvrReturns, ErrorString As String)
Dim sMsg As String
Dim result As VbMsgBoxResult
m_bFatalError = True
UnFreeze
If m_cLanguageText Is Nothing Then
GoTo TheEnd 'Form not yet loaded - not yet logged on
End If
' m_NumFatalErrors = m_NumFatalErrors + 1
' If m_NumFatalErrors > 5 Then
' Functions.DevInfo "Unable to restart Manitou.", g_cLangText_General
' End
' End If
If Error <> SVRERR_NOT_CONNECTED Or RunningInDebugger() Then
sMsg = g_cLangText_General.GetText("A system error has occurred")
If ErrorString <> "" Then
sMsg = sMsg & ":" & vbCrLf & vbCrLf & ErrorString & vbCrLf & vbCrLf
Else
sMsg = sMsg & ". "
End If
sMsg = sMsg & g_cLangText_General.GetText("Press OK to attempt to restart or Cancel to quit.")
result = DevAskOkCancel(sMsg, Nothing)
Else
' Since we've been disconnected, attempt immediately to reconnect
result = vbOK
End If
If (result = vbOK) Then
On Local Error Resume Next
If InStr(g_CommandLine, "-U") = 0 Then
g_CommandLine = g_CommandLine & " -U" & g_cUser.id
End If
If InStr(g_CommandLine, "-P") = 0 Then
g_CommandLine = g_CommandLine & " -P" & g_cUser.Password
End If
Shell App.Path & "\" & App.EXEName & " " & g_CommandLine & " -X", vbNormalFocus
DoEvents
End If
TheEnd:
If (Not RunningInDebugger()) Then
' Running as compiled executable
' Specifies the exit code for the process, and for all threads that
' are terminated as a result of this call. Use the GetExitCodeProcess
' function to retrieve the process's exit value. Use the GetExitCodeThread
' function to retrieve a thread's exit value.
CoUninitialize
ExitProcess 0
Else
' Running from the IDE
End
End If
End Sub
Note that I added the CoUninitialize and ExitProcess 0 API calls to this. How can I correctly terminate the previously loaded instance when the service comes back up? thanks Larry
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
Private Const WM_CLOSE = &H10
Private Sub Form_Load()
Dim strCap As String
Dim lngHwnd As Long
Dim lngRet As Long
strCap = Me.Caption
Me.Caption = "*" & strCap
lngHwnd = FindWindow(ByVal vbNullString, ByVal strCap)
If lngHwnd <> 0 Then
PostMessage lngHwnd, WM_CLOSE, 0&, 0&
End If
Me.Caption = strCap
End Sub
Well... I think the title says all. I wanna check if a pc exists on my network, for example "JOAN-PC".
Now I'm doing something like this:
Dim oShell As Object
Set oShell = CreateObject("Shell.Application")
MsgBox Not CBool(oShell.NameSpace(CVar("\\JOAN-PC")) Is Nothing)
Works good, but is slow, and my program have to call it a lot of times.
Some of you know a fast way to do the same thing?
Thanks in advance.
Perhaps you could use NetRemoteTOD or a related simple network API, even a "ping" request.
Here's a small example you might adapt. Give it a try, the timeout for machines that don't respond doesn't seem too long (7 or 8 seconds). For legit uses this probably won't be an issue, but it is long enough to discourage malicious "scanners" trying to sweep whole networks by IP address for victim machines.
Option Explicit
'Fetch and display Net Remote Time Of Day from a
'remote Windows system. Supply a UNC hostname,
'DNS name, or IP address - or empty string for
'the local host's time and date.
'
'Form has 3 controls:
'
' txtServer TextBox
' cmdGetTime CommandButton
' lblTime Label
Private Const NERR_SUCCESS As Long = 0
Private Type TIME_OF_DAY_INFO
tod_elapsedt As Long
tod_msecs As Long
tod_hours As Long
tod_mins As Long
tod_secs As Long
tod_hunds As Long
tod_timezone As Long
tod_tinterval As Long
tod_day As Long
tod_month As Long
tod_year As Long
tod_weekday As Long
End Type
Private Declare Function NetApiBufferFree Lib "netapi32" ( _
ByVal lpBuffer As Long) As Long
Private Declare Function NetRemoteTOD Lib "netapi32" ( _
ByRef UncServerName As Byte, _
ByRef BufferPtr As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
ByRef pTo As Any, _
ByRef uFrom As Any, _
ByVal lSize As Long)
Private Function GetTOD(ByVal Server As String) As Date
Dim bytServer() As Byte
Dim lngBufPtr As Long
Dim todReturned As TIME_OF_DAY_INFO
bytServer = Trim$(Server) & vbNullChar
If NetRemoteTOD(bytServer(0), lngBufPtr) = NERR_SUCCESS Then
CopyMemory todReturned, ByVal lngBufPtr, LenB(todReturned)
NetApiBufferFree lngBufPtr
With todReturned
GetTOD = DateAdd("n", _
-.tod_timezone, _
DateSerial(.tod_year, .tod_month, .tod_day) _
+ TimeSerial(.tod_hours, .tod_mins, .tod_secs))
End With
Else
Err.Raise vbObjectError Or &H2000&, _
"GetTOD", _
"Failed to obtain time from server"
End If
End Function
Private Sub cmdGetTime_Click()
Dim dtServerTime As Date
On Error Resume Next
dtServerTime = GetTOD(txtServer.Text)
If Err.Number <> 0 Then
lblTime.Caption = Err.Description
Else
lblTime.Caption = CStr(dtServerTime)
End If
On Error GoTo 0
txtServer.SetFocus
End Sub
I want to fill the combo box with all the installed network adapter name using visual basic 6.0. Is there any way to do so? I also want to know how to add, edit and delete the value of registry?
Simplest way would be to shell out to the ipconfig command, redirect the output to a file, and then parse the file. There are many implementations of a function usually called "ShellAndWait()". I've taken one I had squirreled away - it might not be the best, but it works.
Option Explicit
Private Declare Function CloseHandle Lib "Kernel32.dll" ( _
ByVal hHandle As Long _
) As Long
Private Declare Function OpenProcess Lib "Kernel32.dll" ( _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long _
) As Long
Private Declare Function WaitForSingleObject Lib "Kernel32.dll" ( _
ByVal hHandle As Long, _
ByVal dwMilliseconds As Long _
) As Long
Private Const INFINITE As Long = -1&
Private Const SYNCHRONIZE As Long = &H100000
Private Sub Form_Load()
Dim oNetworkAdapters As VBA.Collection
Dim vNetworkAdapter As Variant
Set oNetworkAdapters = GetNetworkAdapters()
cmbNICs.Clear
For Each vNetworkAdapter In oNetworkAdapters
cmbNICs.AddItem vNetworkAdapter
Next vNetworkAdapter
End Sub
Public Function GetNetworkAdapters() As VBA.Collection
Dim sTempFileName As String
Dim nFileNo As Integer
Dim sLine As String
Dim oNetworkAdapters As VBA.Collection
Set oNetworkAdapters = New VBA.Collection
sTempFileName = Environ$("TEMP") & "\VBTmp" & Format$(Now, "yyyymmddhhnnss")
If ShellAndWait("cmd.exe /c ipconfig > """ & sTempFileName & """", vbHide) Then
nFileNo = FreeFile
Open sTempFileName For Input As #nFileNo
Do Until EOF(nFileNo)
Line Input #nFileNo, sLine
If Len(sLine) > 0 Then
If sLine Like "*:" Then
If Not sLine Like " *:" Then
oNetworkAdapters.Add sLine
End If
End If
End If
Loop
Close #nFileNo
Kill sTempFileName
End If
Set GetNetworkAdapters = oNetworkAdapters
End Function
' Start the indicated program and wait for it to finish, hiding while we wait.
Public Function ShellAndWait(ByRef in_sProgramName As String, _
ByVal in_enmWindowStyle As VbAppWinStyle) As Boolean
Dim nProcessId As Long
Dim hProcess As Long
' Start the program.
On Error GoTo ShellError
nProcessId = Shell(in_sProgramName, in_enmWindowStyle)
On Error GoTo 0
DoEvents
' Wait for the program to finish.
' Get the process handle.
hProcess = OpenProcess(SYNCHRONIZE, 0, nProcessId)
If hProcess <> 0 Then
WaitForSingleObject hProcess, INFINITE
CloseHandle hProcess
End If
ShellAndWait = True
Exit Function
ShellError:
MsgBox "Error starting task '" & in_sProgramName & "'" & vbCrLf & Err.Description, vbOKOnly Or vbExclamation, "Error"
End Function
Here is the simple code that will detect all Ethernet and wireless adaptors
NetworkInterface slectedNic;
IEnumerable<NetworkInterface> nics = NetworkInterface.GetAllNetworkInterfaces().Where(network => network.OperationalStatus == OperationalStatus.Up && (network.NetworkInterfaceType == NetworkInterfaceType.Ethernet || network.NetworkInterfaceType == NetworkInterfaceType.Wireless80211));
foreach (NetworkInterface item in nics)
{
cmbAdptors.Items.Add(item);
}
but if u want to detect only active wireless adaptor
change
.Where(network => network.OperationalStatus == OperationalStatus.Up && (network.NetworkInterfaceType == NetworkInterfaceType.Ethernet || network.NetworkInterfaceType == NetworkInterfaceType.Wireless80211));
to
.Where(network => network.OperationalStatus == OperationalStatus.Up && network.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
I have a small VB6 app in which I use the Shell command to execute a program. I am storing the output of the program in a file. I am then reading this file and putting the output on the screen using a msgbox in VB6.
This is what my code looks like now:
sCommand = "\evaluate.exe<test.txt "
Shell ("cmd.exe /c" & App.Path & sCommand)
MsgBox Text2String(App.Path & "\experiments\" & genname & "\freq")
The problem is that the output which the VB program is printing using the msgbox is the old state of the file. Is there some way to hold the execution of the VB code until my shell command program finishes so that I get the correct state of the output file and not a previous state?
The secret sauce needed to do this is the WaitForSingleObject function, which blocks execution of your application's process until the specified process completes (or times out). It's part of the Windows API, easily called from a VB 6 application after adding the appropriate declaration to your code.
That declaration would look something like this:
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle _
As Long, ByVal dwMilliseconds As Long) As Long
It takes two parameters: a handle to the process that you want to wait on, and the time-out interval (in milliseconds) that indicates the maximum amount of time that you want to wait. If you do not specify a time-out interval (a value of zero), the function does not wait and returns immediately. If you specify an infinite time-out interval, the function returns only when the process signals that it has completed.
Armed with that knowledge, the only task that remains is figuring out how to get a handle to the process that you started. That turns out to be pretty simple, and can be accomplished a number of different ways:
One possibility (and the way I'd do it) is by using the ShellExecuteEx function, also from the Windows API, as a drop-in replacement for the Shell function that is built into VB 6. This version is far more versatile and powerful, yet just as easily called using the appropriate declaration.
It returns a handle to the process that it creates. All you have to do is pass that handle to the WaitForSingleObject function as the hHandle parameter, and you're in business. Execution of your application will be blocked (suspended) until the process that you've called terminates.
Another possibility is to use the CreateProcess function (once again, from the Windows API). This function creates a new process and its primary thread in the same security context as the calling process (i.e., your VB 6 application).
Microsoft has published a knowledge base article detailing this approach that even provides a complete sample implementation. You can find that article here: How To Use a 32-Bit Application to Determine When a Shelled Process Ends.
Finally, perhaps the simplest approach yet is to take advantage of the fact that the built-in Shell function's return value is an application task ID. This is a unique number that identifies the program you started, and it can be passed to the OpenProcess function to obtain a process handle that can be passed to the WaitForSingleObject function.
However, the simplicity of this approach does come at a cost. A very significant disadvantage is that it will cause your VB 6 application to become completely unresponsive. Because it will not be processing Windows messages, it will not respond to user interaction or even redraw the screen.
The good folks over at VBnet have made complete sample code available in the following article: WaitForSingleObject: Determine when a Shelled App has Ended.
I'd love to be able to reproduce the code here to help stave off link rot (VB 6 is getting up there in years now; there's no guarantee that these resources will be around forever), but the distribution license in the code itself appears to explicitly forbid that.
There is no need to resort to the extra effort of calling CreateProcess(), etc. This more or less duplicates the old Randy Birch code though it wasn't based on his example. There are only so many ways to skin a cat.
Here we have a prepackaged Function for handy use, which also returns the exit code. Drop it into a static (.BAS) module or include it inline in a Form or Class.
Option Explicit
Private Const INFINITE = &HFFFFFFFF&
Private Const SYNCHRONIZE = &H100000
Private Const PROCESS_QUERY_INFORMATION = &H400&
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
Private Declare Function OpenProcess Lib "kernel32" ( _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" ( _
ByVal hHandle As Long, _
ByVal dwMilliseconds As Long) As Long
Public Function ShellSync( _
ByVal PathName As String, _
ByVal WindowStyle As VbAppWinStyle) As Long
'Shell and wait. Return exit code result, raise an
'exception on any error.
Dim lngPid As Long
Dim lngHandle As Long
Dim lngExitCode As Long
lngPid = Shell(PathName, WindowStyle)
If lngPid <> 0 Then
lngHandle = OpenProcess(SYNCHRONIZE _
Or PROCESS_QUERY_INFORMATION, 0, lngPid)
If lngHandle <> 0 Then
WaitForSingleObject lngHandle, INFINITE
If GetExitCodeProcess(lngHandle, lngExitCode) <> 0 Then
ShellSync = lngExitCode
CloseHandle lngHandle
Else
CloseHandle lngHandle
Err.Raise &H8004AA00, "ShellSync", _
"Failed to retrieve exit code, error " _
& CStr(Err.LastDllError)
End If
Else
Err.Raise &H8004AA01, "ShellSync", _
"Failed to open child process"
End If
Else
Err.Raise &H8004AA02, "ShellSync", _
"Failed to Shell child process"
End If
End Function
I know it's an old thread, but...
How about using the Windows Script Host's Run method? It has a bWaitOnReturn parameter.
object.Run (strCommand, [intWindowStyle], [bWaitOnReturn])
Set oShell = CreateObject("WSCript.shell")
oShell.run "cmd /C " & App.Path & sCommand, 0, True
intWindowStyle = 0, so cmd will be hidden
Do like this :
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 WaitForSingleObject Lib "kernel32" (ByVal _
hHandle As Long, ByVal dwMilliseconds As Long) As Long
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
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
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const INFINITE = -1&
Public Function ExecCmd(cmdline$)
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
' 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)
ExecCmd = ret&
End Function
Sub Form_Click()
Dim retval As Long
retval = ExecCmd("notepad.exe")
MsgBox "Process Finished, Exit Code " & retval
End Sub
Reference : http://support.microsoft.com/kb/129796
Great code. Just one tiny little problem: you must declare in the ExecCmd (after Dim start As STARTUPINFO):
Dim ret as Long
You will get an error when trying to compile in VB6 if you don't.
But it works great :)
Kind regards
In my hands, the csaba solution hangs with intWindowStyle = 0, and never passes control back to VB. The only way out is to end process in taskmanager.
Setting intWindowStyle = 3 and closing the window manually passes control back
I've found a better & simpler solution:
Dim processID = Shell("C:/path/to/process.exe " + args
Dim p As Process = Process.GetProcessById(processID)
p.WaitForExit()
and then you just continue with your code.
Hope it helps ;-)
Other than looping from 1 to 32 and trying open each of them, is there a reliable way to get COM ports on the system?
I believe under modern windows environments you can find them in the registry under the following key HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM. I'm not sure of the correct way to specify registry keys. However I have only ever tested this on Windows XP.
Check out this article from Randy Birch's site: CreateFile: Determine Available COM Ports
There's also the approach of using an MSCOMM control: ConfigurePort: Determine Available COM Ports with the MSCOMM Control
The code's a bit too long for me to post here but the links have everything you need.
It's 1 to 255. Fastest you can do it is using QueryDosDevice like this
Option Explicit
'--- for CreateFile
Private Const GENERIC_READ As Long = &H80000000
Private Const GENERIC_WRITE As Long = &H40000000
Private Const OPEN_EXISTING As Long = 3
Private Const INVALID_HANDLE_VALUE As Long = -1
'--- error codes
Private Const ERROR_ACCESS_DENIED As Long = 5&
Private Const ERROR_GEN_FAILURE As Long = 31&
Private Const ERROR_SHARING_VIOLATION As Long = 32&
Private Const ERROR_SEM_TIMEOUT As Long = 121&
Private Declare Function QueryDosDevice Lib "kernel32" Alias "QueryDosDeviceA" (ByVal lpDeviceName As Long, ByVal lpTargetPath As String, ByVal ucchMax As Long) As Long
Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Function PrintError(sFunc As String)
Debug.Print sFunc; ": "; Error
End Function
Public Function IsNT() As Boolean
IsNT = True
End Function
Public Function EnumSerialPorts() As Variant
Const FUNC_NAME As String = "EnumSerialPorts"
Dim sBuffer As String
Dim lIdx As Long
Dim hFile As Long
Dim vRet As Variant
Dim lCount As Long
On Error GoTo EH
ReDim vRet(0 To 255) As Variant
If IsNT Then
sBuffer = String$(100000, 1)
Call QueryDosDevice(0, sBuffer, Len(sBuffer))
sBuffer = Chr$(0) & sBuffer
For lIdx = 1 To 255
If InStr(1, sBuffer, Chr$(0) & "COM" & lIdx & Chr$(0), vbTextCompare) > 0 Then
vRet(lCount) = "COM" & lIdx
lCount = lCount + 1
End If
Next
Else
For lIdx = 1 To 255
hFile = CreateFile("COM" & lIdx, GENERIC_READ Or GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0)
If hFile = INVALID_HANDLE_VALUE Then
Select Case Err.LastDllError
Case ERROR_ACCESS_DENIED, ERROR_GEN_FAILURE, ERROR_SHARING_VIOLATION, ERROR_SEM_TIMEOUT
hFile = 0
End Select
Else
Call CloseHandle(hFile)
hFile = 0
End If
If hFile = 0 Then
vRet(lCount) = "COM" & lIdx
lCount = lCount + 1
End If
Next
End If
If lCount = 0 Then
EnumSerialPorts = Split(vbNullString)
Else
ReDim Preserve vRet(0 To lCount - 1) As Variant
EnumSerialPorts = vRet
End If
Exit Function
EH:
PrintError FUNC_NAME
Resume Next
End Function
The snippet falls back to CreateFile on 9x. IsNT function is stubbed for brevity.
Using VB6 or VBScript to enumerate available COM ports can be as simple as using VB.NET, and this can be done by enumerating values from registry path HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM. It's better than calling QueryDosDevice() and doing string comparison to filter out devices which's name is leading by COM since you will get something like CompositeBattery (or other stuff which have full upper case name leading by COM) that isn't a COM port. Another benefit of doing this is that the registry values also containing USB to COM devices, which could not be detected by using the codes such as WMIService.ExecQuery("Select * from Win32_SerialPort"). If you try to plug the USB to COM devices in or out of the computer, you can see the registry values also appear or disappear immediately, since it's keeping updated.
Option Explicit
Sub ListComPorts()
List1.Clear
Dim Registry As Object, Names As Variant, Types As Variant
Set Registry = GetObject("winmgmts:\\.\root\default:StdRegProv")
If Registry.EnumValues(&H80000002, "HARDWARE\DEVICEMAP\SERIALCOMM", Names, Types) <> 0 Then Exit Sub
Dim I As Long
If IsArray(Names) Then
For I = 0 To UBound(Names)
Dim PortName As Variant
Registry.GetStringValue &H80000002, "HARDWARE\DEVICEMAP\SERIALCOMM", Names(I), PortName
List1.AddItem PortName & " - " & Names(I)
Next
End If
End Sub
Private Sub Form_Load()
ListComPorts
End Sub
The code above is using StdRegProv class to enumerate the values of a registry key. I've tested the code in XP, Windows 7, Windows 10, and it works without any complainant. The items which were added to the Listbox looks like below:
COM1 - \Device\Serial0
COM3 - \Device\ProlificSerial0
The downside of this code is that it could not detect which port is already opened by other programs since every port could only be opened once. The way to detect a COM port is opened by another program or not can be done by calling the API CreateFile. Here is an example.