c# 使用特定帳號密碼訪問Windows網路共享
透過程式存取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\,為省去人工截取的麻煩,我寫瞭一段正則表達式更順手。
完整程式如下,需要的朋友請自取使用:
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 } }
以上就是c# 使用特定帳號密碼訪問Windows網路共享的詳細內容,更多關於c# 訪問Windows網路共享的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Java中關於文件路徑讀取問題的分析
- Mysql存儲二進制對象數據問題
- ASP.NET對Cookie的操作
- 輕量級ORM框架Dapper應用之實現CURD操作
- SpringBoot 如何使用RestTemplate發送Post請求