Monday, May 28, 2012

Resetting local Ports and Devices from .NET

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)
    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");
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

8 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. Hi Shay. But how should this be possible? The post you referenced is from october 2013. My post was written in may 2012. As far as I know, I have no time machine that allows me to travel to the future. I would really appreciate it for the future if you check you statements before blaming someone.

      Delete
    2. Ok ok, you didn't take it from here, but all the unexplained method calls in your article (methods you can find in some other similar articles) are an obvious proof that you took your code from another article (GetGuidFromName, GetDeviceInfoData, etc)

      Delete
    3. Did you really read my article? Of course, I took some code from other posts, but there are the corresponding references in the article (e.g. http://www.eggheadcafe.com/community/csharp/2/10315145/enabledisable-comm-port-by-programming.aspx). These other posts were the foundation for the solution I came up with and that helped me back then when I needed that functionality in a project. I would never (really never) just take some code or other input from others without at least referencing that other source. This is just courteous.

      Delete
  3. Daniel, I found this article and was very excited to find a way to programmatically reset COM ports since my company has a system that uses serial comms from a webAPI process and those ports occasionally will lock up. I incorporated your code into our pre-connection tests and have found that the calls to TryResetPortByName don't seem to work. I continue to get UnauthorizedAccessExceptions when I try to open the port. Any suggestions?

    Thanks,
    Matthew Belk

    ReplyDelete
    Replies
    1. Hi Matthew, the blog is old but the topic is still relevant. I also received an UnauthorizedAccessException and maybe following statement applies to the `SetupDiCallClassInstaller(DiFunction.PropertyChange, ...)` call:
      > The caller of SetupDiChangeState must be a member of the Administrators group.
      See https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdichangestate#remarks

      Delete