Currently, I am working on C# applications that communicate with several external devices connected via USB ports. In rare cases the ports just stop working correctly, so we needed a programmatic approach to reset them. Doing this by using C# is not trivial. The solution we implemented uses the following components:
- C#’s SerialPort class
- WMI (Windows Management Instrumentation)
- P/Invoke with calls to the Windows Setup API
Accessing a port using C#
Using a certain port with C# is rather simple. The .NET framework provides the SerialPort class as a wrapper to the underlying Win32 port API. You can simply create a new instance of the class by providing the name of an existing port. On the instance object you have methods to open or close the port or to read and write data from its underlying stream. It also provides events to notify any listeners when data or errors were received. Here is a small example on how to open a port by first checking if the given port name exists in the operating system:
public SerialPort OpenPort(string portName)
{
if (!IsPortAvailable(portName))
{
return null;
}
var port = new SerialPort(portName);
try
{
port.Open();
}
catch (UnauthorizedAccessException) { ... }
catch (IOException) { ... }
catch (ArgumentException) { ... }
}
private bool IsPortAvailable(string portName)
{
// Retrieve the list of ports currently mounted by
// the operating system (sorted by name)
// the operating system (sorted by name)
string[] ports = SerialPort.GetPortNames();
if (ports != null && ports.Length > 0)
{
return ports.Where(new Func<string, bool>((s) =>
{
return s.Equals(portName,
StringComparison.InvariantCultureIgnoreCase);
})).Count() == 1;
}
return false;
}
In rare cases the Open method of the port threw an IO Exception in our applications. This situation was not reproducible but also not acceptable. We noticed that after deactivating the port in the device manager and reactivating it, everything was working fine again. So we searched for a solution to do exactly the same thing from code.
Enable/Disable Devices
First of all a function or set of functions was needed to disable and enable a certain port. This can not directly be done from C# and needs some P/Invoke calls to the Win32 API. Luckily others had similar problems and so I found a very good solution at http://www.eggheadcafe.com/community/csharp/2/10315145/enabledisable-comm-port-by-programming.aspx. It uses the device installation functions from the Win32 Setup API. All you need is to retrieve the class GUID for the device set and the instance Id for the specific device you want to disable or enable. Just have a look at the link for the code or download the accompanying code for this blog post.
Retrieving the Ports Instance Id
Last thing to do is to acquire the correct instance Id for our port. We need a method that takes in the port name and retrieves the corresponding instance Id. For the exact definition of an instance Id in windows terms have a look at http://msdn.microsoft.com/en-us/library/windows/hardware/ff541224(v=vs.85).aspx. In our case we’d like to use the Plug ‘n’ Play device Id that can also be seen in the properties window of a device inside the device manager. For this purpose we are going to use WMI. If you need information about WMI have a look at http://msdn.microsoft.com/en-us/library/windows/desktop/aa394582(v=vs.85).aspx. WMI provides the Win32_SerialPort class that can be used to iterate over all mounted ports of the operating system. Two properties of the Win32_SerialPort class are important for us: The DeviceID property which represents the port name and the PNPDeviceID which gives the Plug ‘n’ Play instance Id. Notice while this works perfectly for Plug 'n' Play devices it may not work for other kinds of devices.
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("select * from Win32_SerialPort");
new ManagementObjectSearcher("select * from Win32_SerialPort");
foreach (ManagementObject port in searcher.Get())
{
if (port["DeviceID"].ToString().Equals(portName))
{
instanceId = port["PNPDeviceID"].ToString();
break;
}
}
If we found the appropriate instance Id we can use it together with the Win32 Setup API to retrieve a device info set and the corresponding device info data. If a device info for the instance Id was found we can use its class GUID and the instance Id to disable and enable the device. The following method is used to reset a port with a given instance Id:
public static bool TryResetPortByInstanceId(string instanceId)
{
SafeDeviceInfoSetHandle diSetHandle = null;
if (!String.IsNullOrEmpty(instanceId))
{
try
{
Guid[] guidArray = GetGuidFromName("Ports");
//Get the handle to a device information set for all
//devices matching classGuid that are present on the
//system.
diSetHandle = NativeMethods.SetupDiGetClassDevs(
ref guidArray[0],
null,
IntPtr.Zero,
SetupDiGetClassDevsFlags.DeviceInterface);
//Get the device information data for each matching device.
DeviceInfoData[] diData = GetDeviceInfoData(diSetHandle);
//Try to find the object with the same instance Id.
foreach (var infoData in diData)
{
var instanceIds =
GetInstanceIdsFromClassGuid(infoData.ClassGuid);
foreach (var id in instanceIds)
{
if (id.Equals(instanceId))
{
//disable port
SetDeviceEnabled(infoData.ClassGuid, id, false);
//wait some milliseconds
Thread.Sleep(200);
//enable port
SetDeviceEnabled(infoData.ClassGuid, id, true);
return true;
}
}
}
}
catch (Exception)
{
return false;
}
finally
{
if (diSetHandle != null)
{
if (diSetHandle.IsClosed == false)
{
diSetHandle.Close();
}
diSetHandle.Dispose();
}
}
}
return false;
}
With the code set up so far we can now easily reset a port in case we’re getting an IO Exception while trying to open the port. We just have to call the method inside our exception handler and try to open the port again. That solved our initial problem. Be aware that it may take some time to disable and re-enable the port. It may be a good idea to do it inside a separate thread if you’re working inside an application using a GUI.
Download the sample code file: PortHelper.cs
Download the sample code file: PortHelper.cs