Network folder browser service

2019-06-08 00:26发布

问题:

there are some similar questions to this on the net - even a few here, but even though the askers seem happy I can't find one that actually does what I need.

I'm trying to add a remote directory browsing feature to a web-based administration control panel (intranet based).

I don't need to worry about security at this point as this is handled elsewhere.

To do this I'm using a webservice which accepts a server name and a share/folder path as parameters. I just need it to return the subdirectories of this path, if any.

Doesn't sound so hard, does it? Well, it is (at least to me!)

The only bit I need help with is actually producing a list of directories for the server and path supplied.

All help is appreciated, but please don't just link to a site as I've probably seen it already but failed to get a working solution; most of these don't even seem to attempt to do what the title implies. Some explaination would be helpful as well!

Cheers

回答1:

You can use method described here using Interop.

I made a few modifications to the code to come up with this. I have not extensively tested this, so it may have errors but it should get you started.

private List<string> GetSubDirectories(string serverName, string folderPath)
{
        List<string> subDirectories = new List<string>();
        string folder_path = Path.Combine(serverName, folderPath);            
        IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

        WIN32_FIND_DATA findData;

        IntPtr findHandle;

        findHandle = FindFirstFile(folder_path, out findData);
        if (findHandle == INVALID_HANDLE_VALUE)
        {
            int error = Marshal.GetLastWin32Error();
            Console.WriteLine(error.ToString());
            return null;
        }

        do
        {
            try
            {
                if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
                    subDirectories.Add(findData.cFileName);                    
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
        while (FindNextFile(findHandle, out findData));
        FindClose(findHandle);

        return subDirectories;
    }

    public const int FILE_ATTRIBUTE_DIRECTORY = 0x10;

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern bool FindClose(IntPtr hFindFile);

    [StructLayout(LayoutKind.Sequential)]
    public struct FILETIME
    {
        public uint dwLowDateTime;
        public uint dwHighDateTime;
    };

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct WIN32_FIND_DATA
    {
        public uint dwFileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }


    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern bool CloseHandle(IntPtr handle);

}

You can call it like:

var subdirectories = GetSubDirectories(@"\\[serverName]", @"[folderPath]\*");

You have to add "\*" as per MSDN

On network shares, you can use an lpFileName in the form of the following: "\Server\Share*". However, you cannot use an lpFileName that points to the share itself; for example, "\Server\Share" is not valid.



回答2:

To enumerate subfolders of specified folder in .NET you can use for example DirectoryInfo.EnumerateDirectories method.

To enumerate shares of some computer you can use WNetEnumResource native function if hidden administrative shares like C$, ADMIN$, print$ and so on are not important for you or use NetShareEnum to enumerate all shares.

The corresponding code could be

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.InteropServices;

namespace Subfolders {
    static internal class Native {
        [DllImport ("Netapi32.dll", SetLastError = true)]
        internal static extern uint NetApiBufferFree (IntPtr buffer);

        [DllImport ("Netapi32.dll", CharSet = CharSet.Unicode)]
        internal static extern uint NetShareEnum (
             string serverName,
             int level,
             ref IntPtr bufPtr,
             uint prefmaxlen,
             ref int entriesread,
             ref int totalentries,
             ref int resumeHandle
        );

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

        [DllImport ("MPR.dll", CharSet = CharSet.Auto)]
        internal static extern uint WNetOpenEnum(ResourceScope dwScope, ResourceType dwType, ResourceUsage dwUsage,
            IntPtr lpNetResource, out IntPtr lphEnum);

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

        internal const uint MaxPreferredLength = 0xFFFFFFFF;
        internal const int NerrSuccess = 0;
        internal enum NetError : uint {
            NerrSuccess = 0,
            NerrBase = 2100,
            NerrUnknownDevDir = (NerrBase + 16),
            NerrDuplicateShare = (NerrBase + 18),
            NerrBufTooSmall = (NerrBase + 23),
        }
        internal enum ShareType : uint {
            StypeDisktree = 0,
            StypePrintq = 1,
            StypeDevice = 2,
            StypeIpc = 3,
            StypeSpecial = 0x80000000,
        }
        [StructLayout (LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct ShareInfo1 {
            public string shi1_netname;
            public uint shi1_type;
            public string shi1_remark;
            public ShareInfo1 (string sharename, uint sharetype, string remark) {
                shi1_netname = sharename;
                shi1_type = sharetype;
                shi1_remark = remark;
            }
            public override string ToString () {
                return shi1_netname;
            }
        }
        public enum ResourceScope: uint {
            ResourceConnected = 0x00000001,
            ResourceGlobalnet = 0x00000002,
            ResourceRemembered = 0x00000003,
            ResourceRecent = 0x00000004,
            ResourceContext = 0x00000005
        }
        public enum ResourceType: uint {
            ResourcetypeAny = 0x00000000,
            ResourcetypeDisk = 0x00000001,
            ResourcetypePrint = 0x00000002,
            ResourcetypeReserved = 0x00000008,
            ResourcetypeUnknown = 0xFFFFFFFF
        }
        public enum ResourceUsage: uint {
            ResourceusageConnectable = 0x00000001,
            ResourceusageContainer = 0x00000002,
            ResourceusageNolocaldevice = 0x00000004,
            ResourceusageSibling = 0x00000008,
            ResourceusageAttached = 0x00000010,
            ResourceusageAll = (ResourceusageConnectable | ResourceusageContainer | ResourceusageAttached),
            ResourceusageReserved = 0x80000000
        }
        public enum ResourceDisplaytype: uint {
            ResourcedisplaytypeGeneric = 0x00000000,
            ResourcedisplaytypeDomain = 0x00000001,
            ResourcedisplaytypeServer = 0x00000002,
            ResourcedisplaytypeShare = 0x00000003,
            ResourcedisplaytypeFile = 0x00000004,
            ResourcedisplaytypeGroup = 0x00000005,
            ResourcedisplaytypeNetwork = 0x00000006,
            ResourcedisplaytypeRoot = 0x00000007,
            ResourcedisplaytypeShareadmin = 0x00000008,
            ResourcedisplaytypeDirectory = 0x00000009,
            ResourcedisplaytypeTree = 0x0000000A,
            ResourcedisplaytypeNdscontainer = 0x0000000B
        }
        [StructLayout (LayoutKind.Sequential)]
        public struct NetResource {
            public ResourceScope dwScope;
            public ResourceType dwType;
            public ResourceDisplaytype dwDisplayType;
            public ResourceUsage dwUsage;
            [MarshalAs (UnmanagedType.LPTStr)]
            public string lpLocalName;
            [MarshalAs (UnmanagedType.LPTStr)]
            public string lpRemoteName;
            [MarshalAs (UnmanagedType.LPTStr)]
            public string lpComment;
            [MarshalAs (UnmanagedType.LPTStr)]
            public string lpProvider;
        }
    }
    class Program {
        static IEnumerable<string> GetShares(string computerName) {
            var resources = new List<string>();
            IntPtr hEnum = IntPtr.Zero, pResource = IntPtr.Zero;
            try {
                var resource = new Native.NetResource();
                int bufferSize = 163840;
                resource.dwType = Native.ResourceType.ResourcetypeAny;
                resource.dwScope = Native.ResourceScope.ResourceGlobalnet;
                resource.dwUsage = Native.ResourceUsage.ResourceusageContainer;
                resource.lpRemoteName = computerName;
                pResource = Marshal.AllocHGlobal(Marshal.SizeOf(resource));
                Marshal.StructureToPtr (resource, pResource, false);
                uint status = Native.WNetOpenEnum (Native.ResourceScope.ResourceGlobalnet,
                                                   Native.ResourceType.ResourcetypeDisk,
                                                   0,
                                                   pResource,
                                                   out hEnum);
                if (status != 0)
                    return resources;

                int numberOfEntries = -1;
                IntPtr pBuffer = Marshal.AllocHGlobal(bufferSize);
                status = Native.WNetEnumResource (hEnum, ref numberOfEntries, pBuffer, ref bufferSize);
                if (status == Native.NerrSuccess && numberOfEntries > 0) {
                    var ptr = pBuffer;
                    for (int i = 0; i < numberOfEntries; i++, ptr += Marshal.SizeOf(resource)) {
                        resource = (Native.NetResource)Marshal.PtrToStructure (ptr, typeof (Native.NetResource));
                        resources.Add (resource.lpRemoteName.StartsWith (computerName + '\\',
                                                                         StringComparison.OrdinalIgnoreCase)
                                           ? resource.lpRemoteName.Substring (computerName.Length + 1)
                                           : resource.lpRemoteName);
                    }
                }
            } finally {
                if (hEnum != IntPtr.Zero) {
                    Native.WNetCloseEnum (hEnum);
                }
                if (pResource != IntPtr.Zero) {
                    Marshal.FreeHGlobal(pResource);
                }
            }
            return resources;
        }

        static IEnumerable<string> GetAllShares (string computerName) {
            var shares = new List<string> ();
            IntPtr bufPtr = IntPtr.Zero;
            int entriesread = 0;
            int totalentries = 0;
            int resumeHandle = 0;
            int nStructSize = Marshal.SizeOf (typeof (Native.ShareInfo1));
            try {
                uint ret = Native.NetShareEnum (computerName, 1, ref bufPtr,
                    Native.MaxPreferredLength,
                    ref entriesread,
                    ref totalentries,
                    ref resumeHandle);
                if (ret == (uint)Native.NetError.NerrSuccess) {
                    var currentPtr = bufPtr;
                    for (int i = 0; i < entriesread; i++) {
                        var shi1 = (Native.ShareInfo1)Marshal.PtrToStructure (currentPtr, typeof (Native.ShareInfo1));
                        if ((shi1.shi1_type & ~(uint)Native.ShareType.StypeSpecial) == (uint)Native.ShareType.StypeDisktree) {
                            shares.Add (shi1.shi1_netname);
                        }
                        currentPtr = new IntPtr (currentPtr.ToInt32 () + nStructSize);
                    }
                }
            } finally {
                if (bufPtr != IntPtr.Zero)
                    Native.NetApiBufferFree (bufPtr);
            }
            return shares;
        }
        static IEnumerable<string> GetSubdirectories (string root) {
            var dirInfo = new DirectoryInfo (root);
            return (from info in dirInfo.EnumerateDirectories () select info.Name).ToList();
        }
        static void Main () {
            var root = @"\\OK01\Users";
            Console.WriteLine ("Subdirectories of {0}:", root);
            foreach (var dir in GetSubdirectories (root)) {
                Console.WriteLine (dir);
            }

            Console.WriteLine ();
            root = @"\\OK01\Users\Public";
            Console.WriteLine ("Subdirectories of {0}:", root);
            foreach (var dir in GetSubdirectories (root)) {
                Console.WriteLine (dir);
            }

            Console.WriteLine ();
            root = @"\\OK01";
            Console.WriteLine ("All Shares of {0} (inclusive hidden):", root);
            foreach (var shareName in GetAllShares (root)) {
                Console.WriteLine (shareName);
            }

            Console.WriteLine ();
            root = @"\\OK01";
            Console.WriteLine ("Shares of {0}:", root);
            foreach (var shareName in GetShares (root)) {
                Console.WriteLine (shareName);
            }
        }
    }
}

which produce output like

Subdirectories of \\OK01\Users:
All Users
ASP.NET v4.0
Default
Default User
MSSQL$SQL2012
Oleg
Public

Subdirectories of \\OK01\Users\Public:
Desktop
Documents
Downloads
Favorites
Libraries
Music
Pictures
Recorded TV
Roaming
Videos

All Shares of \\OK01 (inclusive hidden):
ADMIN$
C$
print$
Q$
Users
Virtual Machines
VMware

Shares of \\OK01:
Users
Virtual Machines
VMware

The above code is simplified to demonstrate only how to use the corresponding API. It contains no real error reporting.



回答3:

Not sure if we can achieve this. We had similar problem but finally resolved it by providing the shared path (\SERVERNAME\FOLDER).

Most important webservice should use a account that has full permission to access the directory else exception related to permission will be thrown out to calling client.



回答4:

Well, actually it can be done using NetShareEnum Win32API function.

But here is .NET wrapper classes to enumerate network shares on local and remote machines, and convert local file paths to UNC paths. Please see the article Network Shares and UNC paths.