Search code examples
c#.netserial-portioexceptionremote-desktop

Undocumented exception when using COM port redirection through RDP


I've been asked to work on a small tool that one of my predecessors made a long time ago in .NET 3.5 (if it matters, the tool is using winforms). This tool's main feature is to reprogram machines by communicating through a serial port. As such, the machine is connected by a COM/USB cable to the computer, and the computer uses a driver to emulate a COM port on which the tool can read/write.

Because the tool needs to access secure resources, it is run on a distant server through Remote Desktop. We use the serial port redirection feature in Remote Desktop to let the tool communicate on the local computer's emulated COM port, allowing it in turn to communicate with the machine to reprogram.

So, to put it shortly : the machine to reprogram is connected through a COM-to-USB cable on my local computer. A driver on my local computer allows the USB to be used as an emulated COM port. This emulated COM port is forwarded through RDP to the server running the tool. The tool then reads and writes on the COM port.

The issue I'm trying to solve happens right at the start of the process, when trying to read the machine's output through the COM port. The code for the relevant part looks something like this :

serialPort = new SerialPort();
serialPort.PortName = "COM1";
serialPort.BaudRate = 115200;
serialPort.Parity = Parity.None;
serialPort.DataBits = 8;
serialPort.StopBits = StopBits.One;
serialPort.Handshake = Handshake.None;
serialPort.NewLine = "\r";

serialPort.Open();

serialPort.ReadTimeout = 2000;
do
{
    try
    {
        sMessage = serialPort.ReadLine();
        AddLog(sMessage);
    }
    catch (TimeoutException) {}
} while (!sMessage.Contains("Starting Reprogramming Process"));

This seems to work fine on the old Windows Server 2012. However, when the tool is running on the new Windows Server 2019, which is supposed to replace the old one, a System.IO.IOException is immediately thrown from the serialPort.ReadLine() method.

(the error message translates to "End of file reached", sorry about the french translation)

Screen capture of the IOException and its stack pile

I've tried to run the tool on my local computer directly, and it reads from the COM port just fine.

I've used PuTTY to listen directly on the port as well, and at first I couldn't get anything on the remote desktop. This made me suspect a driver issue, which I reinstalled on the server. Doing that now allows me to read the COM port with PuTTY on the remote desktop, but it did not fix the tool's behaviour.

I doublechecked my serial port settings (baudrate, parity...) and they're all good.

The part that's the most confusing to me is that this IOException does not seem to be documented. Either that or I'm working with the wrong documentation?

I've skimmed through other Q&As, to no avail. The closest issue I've found is this one over on Server Fault, but the fact that I can read the COM port through PuTTY seems to point more towards an issue with my code, or with the SerialPort module.

I've ran out of leads to follow. Has anyone ever encountered a similar issue? Or is there a mistake in my code making it both work in certain environments, and not work in others?


Solution

  • Further testing has made it apparent that the tool can, in fact, read from the COM port. Making it so that the machine sends data continuously, then plugging in the tool in the middle of the exchange, makes it able to read the data, as long as it is sent. The exception happens precisely when no data is being sent.

    I'm still stumped as to why an exception would be thrown on certain environments and not on others, but at the very least the data's going through, so I can work with that.

    I've made a "band-aid" solution by simply catching and ignoring this specific exception, using the HResult property of the exception to separate it from other unrelated IOExceptions. The resulting code looks something like this :

    do
    {
        try
        {
            sMessage = serialPort.ReadLine();
            AddLog(sMessage);
        }
        catch (TimeoutException) {}
        catch (IOException e)
        {
            // We do not want to catch all IOExceptions, only the problematic "EoF" exception
            // In .NET 3.5 the "Exception.HResult" property is protected, we need to use reflection to read it
            // Code adapted from this answer : https://stackoverflow.com/a/58560128/11840945
            PropertyInfo piHR = typeof(Exception).GetProperty("HResult", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
    
            if (piHR != null)
            {
                int errorCode = Convert.ToInt32(piHR.GetValue(e, null));
                                
                if (errorCode == -2147024858) // This is how the "ERROR_HANDLE_EOF = 38" internal code is parsed
                {
                    continue;
                }
            }
    
            // If the exception we caught isn't the "EoF" exception, it was unexpected, we throw it
            throw e;
        }
    } while (!sMessage.Contains("Starting Reprogramming Process"));
    

    Having read that using reflection in that way is terrible for performance, I was a bit skeptical at first, but running the tool, it seems to do fine, and works as intended now.