.netpinvokedllimportwtsapi32

Trying to enumerate Terminal Server sessions produces AccessViolationException (.NET and Native DLL)


I am trying to enumerate all remote desktop sessions in C# using the function WtsEnumerateSessionsExA defined in WtsApi32.dll.

This function writes into an array of WTS_SESSION_INFO_1A structs whose pointer is passed as a parameter. You can find the documentation here:

I have implemented the WTS_SESSION_INFO_1A struct and WTS_CONNECT_STATE_CLASS in C# like this:

struct WtsSessionInfoClass
{
    uint ExecEnvId;
    WtsConnectStateClass state;
    uint sessionId;
    string pSessionName;
    string pHostName;
    string pUserName;
    string pDomainName;
    string pFarmName;
}
enum WtsConnectStateClass
{
    WTSActive,
    WTSConnected,
    WTSConnectQuery,
    WTSShadow,
    WTSDisconnected,
    WTSIdle,
    WTSListen,
    WTSReset,
    WTSDown,
    WTSInit
}

I have imported the required functions in a class called Native32:

[DllImport("WtsApi32.dll")]
internal static extern IntPtr WTSOpenServerEx(string name);

[DllImport("WtsApi32.dll")]
internal static extern bool WTSEnumerateSessionsExA(
    IntPtr hServer,
    UIntPtr pLevel,
    uint Filter,
    out WtsSessionInfoClass[] ppSessionInfo,
    UIntPtr pCount
);

Finally my implementation is the following:

UIntPtr len = new UIntPtr();
WtsSessionInfoClass[] sessions;
IntPtr handle = Native32.WTSOpenServerEx("MyMachineName"); // This function gets the server handle and works fine
Native32.WTSEnumerateSessionsExA(handle, new UIntPtr(1), 0, out sessions, len); // This function will produce AccessViolationException
...

When executing the code, the call to Native32.WTSEnumerateSessionsExA fails due to AccessViolationException.

What am I doing wrong here? Any help is appreciated.


Solution

  • My best guess is that you're not passing the second parameter pLevel the right way. You're trying to pass a pointer to a DWORD whose value is 1, but what you're actually passing is the address 1. Instead, declare a local variable initialized to 1 and pass the address of that.

    More details:

    The doc page for WTSEnumerateSessionsExA that you linked says this:

    pLevel

    This parameter is reserved. Always set this parameter to one. On output, WTSEnumerateSessionsEx does not change the value of this parameter.

    The wording isn't the best here, but my interpretation is that you need to pass a valid pointer for pLevel, and that pointer needs to point to a properly aligned DWORD whose value is 1.

    To me, the alternate reading would be that you need to pass the literal memory address 1 as the pointer for pLevel. Which feels instinctively absurd to me.

    However, your code:

    Native32.WTSEnumerateSessionsExA(
      handle, new UIntPtr(1), 0, out sessions, len);
    

    passes in new UIntPtr(1) for pLevel. According to my reading of the UIntPtr doc pages, that expression will create a pointer whose literal value is 1, in other words, the address 1.

    If you debugged into the assembly code of the WTSEnumerateSessionsEx implementation, my guess is that you would see it try to dereference pLevel, and then crash with the access violation you saw when it tries to load a DWORD from address 1.

    To fix the problem, you can declare a uint variable containing the value 1, and pass an address to that variable:

    uint enumerateSessionsLevel = 1;
    Native32.WTSEnumerateSessionsExA(
      handle, &enumerateSessionsLevel, 0, out sessions, len);