46

使用特定帳號密碼存取 Windows 網路分享 (C#)

 3 years ago
source link: https://blog.darkthread.net/blog/csharp-connect-netshare-with-credential/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
使用特定帳號密碼存取 Windows 網路分享 (C#)-黑暗執行緒

透過程式存取 Windows 網路分享的檔案也算常見需求,但存取身分是個問題。之前我慣用的技巧是用有權限的 AD 網域帳號執行排程存取網路分享,但這招要搬進網站或遇到不同網路分享用不同帳號便會破功。最近遇上類似議題,直覺要得回頭靠 WinAPI Impersonation 解決,之前曾寫過通用元件,擔心 11 年前 Windows Vista/7 時代的作品有點過時,就爬文找找更好的做法。

之前的變身做法是改變 Thread 的執行身分,然而針對網路分享還有另一個 WinAPI - WNetAddConnection2,可做到對多個網路分享使用不同登入身分。在 stackoverflow 找到有人分享把它包成搭配 using 使用的 Context 物件,符合我慣用的風格,二話不說,按讚並寫文分享。

為方便使用,我再做了一層包裝,寫了一個 NetworkCopier,將查目錄或複製檔案動作簡化成 Copy(srcNetPath, targetPath, domain, userId, passwd)、DirFiles(srcNetPath, domain, userId, passwd),並支援預設帳密可省略輸入 domain, userId, passwd;另外還有 GetConnetionContext(path, domain, userId, passwd) 可取回 NetworkConnection 物件方便用同一連線身分進行多項操作,若來源與目的屬不同網路分享則可套疊多重身分,寫成:

using (var ctxA = NetworkCopier.GetConnetionContext("\\SvrA\Share", MyAD", "userX", "***")
{
    using (var ctxB = NetworkCopier.GetConnetionContext("\\SvrB\Share", MyAD", "userY", "***")    
    {
        File.Copy(@"\\SvrA\Share\SubFolder\test.txt", @"\\SvrB\Share\test.txt");
        File.Copy(@"\\SvrA\Share\hello.txt", @"\\SvrB\Share\Temp\hello.txt");
    }
}

建立 NetworkConnection 時,需傳入分享路徑而非完整路徑,例如要存取 \\SvrA\Share\SubFolder\test.txt,建立 NetworkConnection 的參數為 \\SvrA\Share\,為省去人工截取的麻煩,我寫了一段 Regular Expression 可自動從完整路徑取出分享路徑,使用上更順手。

完整程式如下,需要的朋友請自取使用:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Web;

namespace MyApp.Models
{
    public class NetworkCopier
    {
        static string defaultDomain = null;
        static string defaultUserId = null;
        static string defaultPasswd = null;
        static NetworkCopier()
        {
            try
            {
                //TODO: 由設定檔、Registry 或 DB 取得帳號設定,密碼記得要加密保存
                var p = ReadCredentialInfo().Split('\t');
                defaultDomain = p[0];
                defaultUserId = p[1];
                defaultPasswd = p[2];
            }
            catch { }
        }
        static string NotNull(string s)
        {
            if (string.IsNullOrEmpty(s)) 
                throw new ApplicationException("未設定預設登入身分");
            return s;
        }
        static string DefaultDomain => NotNull(defaultDomain);
        static string DefaultUserId => NotNull(defaultUserId);
        static string DefaultPassword => NotNull(defaultPasswd);
        static string GetSharePath(string path)
        {
            var m = Regex.Match(path, @"^\\\\[^\\]+\\[^\\]+");
            if (m.Success) return m.Value;
            return path;
        }
        public static void Copy(string srcPath, string dstPath, string domain = null, string userId = null, string passwd = null)
        {
            using (new NetworkConnection(GetSharePath(srcPath), 
                new NetworkCredential(userId ?? DefaultUserId, passwd ?? DefaultPassword, domain ?? DefaultDomain)))
            {
                File.Copy(srcPath, dstPath);
            }
        }
        public static string[] DirFiles(string path, string pattern, string domain = null, string userId = null, string passwd = null)
        {
            using (new NetworkConnection(GetSharePath(path), 
                new NetworkCredential(userId ?? DefaultUserId, passwd ?? DefaultPassword, domain ?? DefaultDomain)))
            {
                return Directory.GetFiles(path, pattern);
            }
        }
        
        public static GetConnectionContext(string path, string domain = null, string userId = null, string passwd = null) 
        {
            return new NetworkConnection(GetSharePath(path), 
                new NetworkCredential(userId ?? DefaultUserId, passwd ?? DefaultPassword, domain ?? DefaultDomain));
        }
        
    }
    //引用來源: https://stackoverflow.com/a/1197430/288936
    public class NetworkConnection : IDisposable
    {
        string _networkName;
        public NetworkConnection(string networkName, NetworkCredential credentials)
        {
            _networkName = networkName;
            var netResource = new NetResource()
            {
                Scope = ResourceScope.GlobalNetwork,
                ResourceType = ResourceType.Disk,
                DisplayType = ResourceDisplaytype.Share,
                RemoteName = networkName
            };
            var userName = string.IsNullOrEmpty(credentials.Domain)
                ? credentials.UserName
                : string.Format(@"{0}\{1}", credentials.Domain, credentials.UserName);
            var result = WNetAddConnection2(
                netResource,
                credentials.Password,
                userName,
                0);
            if (result != 0)
            {
                throw new Win32Exception(result);
            }
        }
        ~NetworkConnection()
        {
            Dispose(false);
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            WNetCancelConnection2(_networkName, 0, true);
        }
        [DllImport("mpr.dll")]
        private static extern int WNetAddConnection2(NetResource netResource, string password, string username, int flags);
        [DllImport("mpr.dll")]
        private static extern int WNetCancelConnection2(string name, int flags, bool force);
    }
    [StructLayout(LayoutKind.Sequential)]
    public class NetResource
    {
        public ResourceScope Scope;
        public ResourceType ResourceType;
        public ResourceDisplaytype DisplayType;
        public int Usage;
        public string LocalName;
        public string RemoteName;
        public string Comment;
        public string Provider;
    }
    public enum ResourceScope : int
    {
        Connected = 1,
        GlobalNetwork,
        Remembered,
        Recent,
        Context
    };
    public enum ResourceType : int
    {
        Any = 0,
        Disk = 1,
        Print = 2,
        Reserved = 8,
    }
    public enum ResourceDisplaytype : int
    {
        Generic = 0x0,
        Domain = 0x01,
        Server = 0x02,
        Share = 0x03,
        File = 0x04,
        Group = 0x05,
        Network = 0x06,
        Root = 0x07,
        Shareadmin = 0x08,
        Directory = 0x09,
        Tree = 0x0a,
        Ndscontainer = 0x0b
    }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK