views:

517

answers:

2

Hi

I am trying to list all local drives in my app, and DriveInfo.GetDrives give back the local drive letters, and I need the mapped drives too. Currently I have: C,D,G (HDD), E,F (CD), and S,Z (Mapped network drives) (Yes, they are all visible in the Explorer, and in Total Commander too) But only the C,D,E,F,G are retreived programmatically.

I tried also Environment.GetLogicalDrives(), GetLogicalDriveStrings (pInvoke), FindFirstVolume-FindNextVolumen (pInvoke). I tried to find the mapped drive list in registry. Nothing works, only the HDD and CD letters are retreived.

There isn't any exception coming, no error indicated in the direct WinAPI calls, and now I am stuck. Is it some kind of security setting? Everywhere I look, the people say that the DriveInfo.GetDrives should give back the mapped drives. Is it really true?

I am using Vista Home Pro. The app is running from the local machine, and it is built here too with VS2k8.

I post what I used, but it is so simple, it cannot be the case I do something wrong: foreach (System.IO.DriveInfo di in System.IO.DriveInfo.GetDrives()) Console.WriteLine(di.Name); Result: C:\ D:\ E:\ F:\ G:\ Press any key to continue . . .

Any ideas?

+3  A: 

Environment.GetLogicalDrives() and DriveInfo.GetDrives() both returned all my networked drives.

Is your application running as a different user (For example an asp.net website)? If it is, are the drives actually mapped for that user? You might find that the drives are mapped for you but they aren't actually mapped for the user your application is running as.

Tim Schneider
No, it is a WinForm application, and I didn't change users.Can this be that the UAC (which I keep turned on) starts my app with the normal user, while the mapped drives are linked to my real (administrator) user? I doubt it... but maybe.
Zolka
I doubt it, you don't generally have to grant administrator access to see file shares. You could always try running the app as an administrator just to see. One thing you could take a look at is System.Security.Principal.WindowsIdentity.GetCurrent().Name which will tell you what user it's currently running as, just to be sure.
Tim Schneider
I tried it without UAC, stilll doesn't work. I checked the current user from the app, and it is the same I was using.By the way, the mapped drives are disconnected. I don't think that should mean any difference, but maybe it helps.
Zolka
Yes, if the mapped drive is disconnected that will make a difference. If your network drive is offline then it's not actually attached and hence isn't a logical drive. You have to go into far messier to do that. The only method I've used for this (WNetRestoreConnectionW) has a big "Not available from vista onwards" sign on it's MSDN page so I'm not 100% sure how you'll do this.
Tim Schneider
+1  A: 

OK, I found out how to get the disconnected drives on Vista. Not easy, but it is doable:

First, you'll need the pInvoke definition of the following WinAPI functions:

  • WNetOpenEnum
  • WNetEnumResource
  • WNetCloseEnum

and those need a struct and some enums as well.

Then you need to call those multiple times, and at the end, you get the list. Here is the code, beware, it is long:

    [DllImport("MPR.dll", CharSet = CharSet.Auto)]
    static extern int WNetEnumResource(IntPtr hEnum, ref int lpcCount, IntPtr lpBuffer, ref int lpBufferSize);

    [DllImport("MPR.dll", CharSet = CharSet.Auto)]
    static extern int WNetOpenEnum(RESOURCE_SCOPE dwScope, RESOURCE_TYPE dwType, RESOURCE_USAGE dwUsage,
        [MarshalAs(UnmanagedType.AsAny)][In] object lpNetResource, out IntPtr lphEnum);

    [DllImport("MPR.dll", CharSet = CharSet.Auto)]
    static extern int WNetCloseEnum(IntPtr hEnum);

    public enum RESOURCE_SCOPE : uint
    {
        RESOURCE_CONNECTED = 0x00000001,
        RESOURCE_GLOBALNET = 0x00000002,
        RESOURCE_REMEMBERED = 0x00000003,
        RESOURCE_RECENT = 0x00000004,
        RESOURCE_CONTEXT = 0x00000005
    }
    public enum RESOURCE_TYPE : uint
    {
        RESOURCETYPE_ANY = 0x00000000,
        RESOURCETYPE_DISK = 0x00000001,
        RESOURCETYPE_PRINT = 0x00000002,
        RESOURCETYPE_RESERVED = 0x00000008,
    }
    public enum RESOURCE_USAGE : uint
    {
        RESOURCEUSAGE_CONNECTABLE = 0x00000001,
        RESOURCEUSAGE_CONTAINER = 0x00000002,
        RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
        RESOURCEUSAGE_SIBLING = 0x00000008,
        RESOURCEUSAGE_ATTACHED = 0x00000010,
        RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
    }
    public enum RESOURCE_DISPLAYTYPE : uint
    {
        RESOURCEDISPLAYTYPE_GENERIC = 0x00000000,
        RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001,
        RESOURCEDISPLAYTYPE_SERVER = 0x00000002,
        RESOURCEDISPLAYTYPE_SHARE = 0x00000003,
        RESOURCEDISPLAYTYPE_FILE = 0x00000004,
        RESOURCEDISPLAYTYPE_GROUP = 0x00000005,
        RESOURCEDISPLAYTYPE_NETWORK = 0x00000006,
        RESOURCEDISPLAYTYPE_ROOT = 0x00000007,
        RESOURCEDISPLAYTYPE_SHAREADMIN = 0x00000008,
        RESOURCEDISPLAYTYPE_DIRECTORY = 0x00000009,
        RESOURCEDISPLAYTYPE_TREE = 0x0000000A,
        RESOURCEDISPLAYTYPE_NDSCONTAINER = 0x0000000B
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct NetResource
    {
        public RESOURCE_SCOPE dwScope;
        public RESOURCE_TYPE dwType;
        public RESOURCE_DISPLAYTYPE dwDisplayType;
        public RESOURCE_USAGE dwUsage;
        [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)]
        public string lpLocalName;
        [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)]
        public string lpRemoteName;
        [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)]
        public string lpComment;
        [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)]
        public string lpProvider;
    }

    static System.Collections.Generic.Dictionary<string, NetResource> WNetResource(object resource)
    {
        System.Collections.Generic.Dictionary<string, NetResource> result = new System.Collections.Generic.Dictionary<string, NetResource>();

        int iRet;
        IntPtr ptrHandle = new IntPtr();
        try
        {
            iRet = WNetOpenEnum(
                RESOURCE_SCOPE.RESOURCE_REMEMBERED, RESOURCE_TYPE.RESOURCETYPE_DISK, RESOURCE_USAGE.RESOURCEUSAGE_ALL,
                resource, out ptrHandle);
            if (iRet != 0)
                return null;

            int entries = -1;
            int buffer = 16384;
            IntPtr ptrBuffer = Marshal.AllocHGlobal(buffer);
            NetResource nr;

            iRet = WNetEnumResource(ptrHandle, ref entries, ptrBuffer, ref buffer);
            while ((iRet == 0) || (entries > 0))
            {
                Int32 ptr = ptrBuffer.ToInt32();
                for (int i = 0; i < entries; i++)
                {
                    nr = (NetResource)Marshal.PtrToStructure(new IntPtr(ptr), typeof(NetResource));
                    if (RESOURCE_USAGE.RESOURCEUSAGE_CONTAINER == (nr.dwUsage
                        & RESOURCE_USAGE.RESOURCEUSAGE_CONTAINER))
                    {
                        //call recursively to get all entries in a container
                        WNetResource(nr);
                    }
                    ptr += Marshal.SizeOf(nr);
                    result.Add(nr.lpLocalName, nr);
                }

                entries = -1;
                buffer = 16384;
                iRet = WNetEnumResource(ptrHandle, ref entries, ptrBuffer, ref buffer);
            }

            Marshal.FreeHGlobal(ptrBuffer);
            iRet = WNetCloseEnum(ptrHandle);
        }
        catch (Exception)
        {
        }

        return result;
    }
    public static System.Collections.Generic.Dictionary<string, NetResource> WNetResource()
    {
        return WNetResource(null);
    }

You've got to call the WNetResource(), and you will get back the list of drives. (and cake :-))

Zolka