PInvoke NetUseEnum

-1

I want to execute NetUseEnum from C#. My objective is to list network shares available to the local computer. My preferences regarding parameters to pass are:

  1. UncServerName = null; (the local computer is used)
  2. LevelFlags = 0; (USE_INFO_0)

What is the correct method signature?

[DllImport("C:\\dev\\netapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint NetUseEnum(
    UnmanagedType uncServerName,
    UInt32 level,
    ref IntPtr buf,
    UInt32 preferedMaximumSize,
    out int entriesRead,
    out int totalEntries,
    IntPtr resumeHandle
);

What is the correct method call?

static void Main(string[] args) {
    string UncServerName;
    IntPtr lBuffer = IntPtr.Zero;
    int lRead;
    int lTotal;
    IntPtr lHandle = IntPtr.Zero;           

    NetUseEnum(null,
               0,
               ref lBuffer,
               0xffffffff,
               out lRead,
               out lTotal,
               lHandle);

    //iterate through lBuffer

    NetApiBufferFree(lBuffer);
}

The code above will not work as posted because the string-handling code needs to be modified.
In particular, the incompatibility occurs between the WinAPI (which prefers working with pointers to Unicode strings, i.e., LPWStr) and C# (which prefers working with strings/Strings).

c#
winapi
pinvoke
asked on Stack Overflow Mar 30, 2021 by nairware • edited Mar 30, 2021 by Jimi

1 Answer

0

NetUseEnum documentation.

You're setting CharSet.Unicode and the UncServerName parameter is an Unicode string if _WIN32_WINNT or FORCE_UNICODE are defined. You can declare UncServerName as string.
You cannot declare it as UnmanagedType, it doesn't make sense. You could specify how to marshal with [MarshalAs(UnmanagedType.LPWStr)], but it's not required.

The declaration of the other members is ~OK.

You're not doing anything with the Buffer after that, though.
This buffer pointer is set when the function is first called: it points to the first structure of the Type specified by LevelFlags USE_INFO_0, USE_INFO_1 or USE_INFO_2.
You can iterate EntriesRead times and move the pointer by the size of structure level specified to read all the entries. For example:

// Local Machine, USE_INFO_2 info level
var connections1 = GetNetConnections<USE_INFO_2>(null, 2);
// A Remote Machine, USE_INFO_0 info level
var connections2 = GetNetConnections<USE_INFO_0>("\\\\REMOTE-SERVER", 0);

// [...]

public List<T> GetNetConnections<T>(string computerName, uint infoLevel) where T : struct
{
    if (typeof(T) != typeof(USE_INFO_0) && typeof(T) != typeof(USE_INFO_2)) {
        throw new ArgumentException("Wrong Type");
    }

    IntPtr buffer = IntPtr.Zero;
    try {
        NetUseEnum(computerName, infoLevel, ref buffer, 
            MAX_PREFERRED_LENGTH, out int entries, out int total, IntPtr.Zero);

        var connections = new List<T>(entries);

        for (int i = 0; i < entries; i++) {
            T info = Marshal.PtrToStructure<T>(IntPtr.Add(buffer, Marshal.SizeOf<T>() * i));
            connections.Add(info);
        }
        return connections;
    }
    finally {
        NetApiBufferFree(buffer);
    }
}

Declarations:

internal const uint MAX_PREFERRED_LENGTH = 0xFFFFFFFF;

[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern uint NetUseEnum(
    string UncServerName,
    uint LevelFlags, 
    ref IntPtr BufPtr, 
    uint PreferedMaximumSize,
    out int EntriesRead, 
    out int TotalEntries, 
    IntPtr ResumeHandle
);

[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern uint NetApiBufferFree(IntPtr buffer);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct USE_INFO_0
{
    string ui0_local;
    string ui0_remote;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct USE_INFO_2
{
    public string ui2_local;
    public string ui2_remote;
    public string ui2_password;
    public uint ui2_status;
    public uint ui2_asg_type;
    public uint ui2_refcount;
    public uint ui2_usecount;
    public string ui2_username;
    public string ui2_domainname;
}
answered on Stack Overflow Mar 30, 2021 by Jimi • edited Mar 30, 2021 by Jimi

User contributions licensed under CC BY-SA 3.0