1. 程式人生 > >C#訪問遠端主機資源的方法

C#訪問遠端主機資源的方法

最近要實現訪問遠端主機的共享目錄中的一個檔案。遇到了許可權問題。google了一下,找到了幾種解決方法,記錄如下:

一、呼叫Net use命令

        // 使用方法:
        //if (Connect("192.168.1.48", "使用者名稱", "密碼"))   
        //{
        //    File.Copy(@"\\192.168.1.48\共享目錄\test.txt",   @"e:\\test.txt",   true);   
        //}
public bool Connect(string remoteHost, string userName, string passWord)
{
    bool Flag = true;
    Process proc = new Process();
    proc.StartInfo.FileName = "cmd.exe";
    proc.StartInfo.UseShellExecute = false;
    proc.StartInfo.RedirectStandardInput = true;
    proc.StartInfo.RedirectStandardOutput = true;
    proc.StartInfo.RedirectStandardError = true;
    proc.StartInfo.CreateNoWindow = true;
    try
    {
        proc.Start();
        string command = @"net  use  \\" + remoteHost + "  " + passWord + "  " + "  /user:" + userName + ">NUL";
        proc.StandardInput.WriteLine(command);
        command = "exit";
        proc.StandardInput.WriteLine(command);
        while (proc.HasExited == false)
        {
            proc.WaitForExit(1000);
        }
        string errormsg = proc.StandardError.ReadToEnd();
        if (errormsg != "")
            Flag = false;
        proc.StandardError.Close();
    }
    catch (Exception ex)
    {
        Flag = false;
    }
    finally
    {
        proc.Close();
        proc.Dispose();
    }
    return Flag;
}

二、呼叫WNetAddConnection2、WNetAddConnection3或者NetUseAdd函式,進行磁碟對映。

using System;
using System.Collections.Generic;
using System.Text;       
using System.Runtime.InteropServices;

namespace WindowsApplication1
{
    public class MyMap
    {
        [DllImport("mpr.dll", EntryPoint = "WNetAddConnection2")]
        public static extern uint WNetAddConnection2(
            [In] NETRESOURCE lpNetResource,
            string lpPassword,
            string lpUsername,
            uint dwFlags);

        [DllImport("Mpr.dll")]
        public static extern uint WNetCancelConnection2(
            string lpName,
            uint dwFlags,
            bool fForce);

        [StructLayout(LayoutKind.Sequential)]
        public class NETRESOURCE
        {
            public int dwScope;
            public int dwType;
            public int dwDisplayType;
            public int dwUsage;
            public string LocalName;
            public string RemoteName;
            public string Comment;
            public string Provider;
        }

        // remoteNetworkPath format:  @"\\192.168.1.48\sharefolder"
        // localDriveName format:     @"E:"
        public static bool CreateMap(string userName, string password, string remoteNetworkPath, string localDriveName)
        {
            NETRESOURCE myNetResource = new NETRESOURCE();
            myNetResource.dwScope = 2;       //2:RESOURCE_GLOBALNET
            myNetResource.dwType = 1;        //1:RESOURCETYPE_ANY
            myNetResource.dwDisplayType = 3; //3:RESOURCEDISPLAYTYPE_GENERIC
            myNetResource.dwUsage = 1;       //1: RESOURCEUSAGE_CONNECTABLE
            myNetResource.LocalName = localDriveName;
            myNetResource.RemoteName = remoteNetworkPath;
            myNetResource.Provider = null;

            uint nret = WNetAddConnection2(myNetResource, password, userName, 0);

            if (nret == 0)
                return true;
            else
                return false;
        }

        // localDriveName format:     @"E:"
        public static bool DeleteMap(string localDriveName)
        {
            uint nret = WNetCancelConnection2(localDriveName, 1, true);

            if (nret == 0)
                return true;
            else
                return false;
        }

        public void test()
        {
            // 注意:
            // remote、local、username的格式一定要正確,否則可能出現錯誤
            string remote = @"\\192.168.1.48\generals";
            string local = @"P:";
            string username = @"Domain\UserName";
            string password = @"Password";
            bool ret = MyMap.CreateMap(username, password, remote, local);
            if (ret)
            {
                //do what you want:
                // ...
                //File.Copy("q:\\test.htm", "c:\\test.htm");

                MyMap.DeleteMap(local);
            }
        }
    }
}

三、使用WebClient類

由於WebClient類可以上傳下載檔案,並且支援以http:、https:和file:開頭的URI,所以可以用WebClient類來傳輸檔案。
新增System.Net名稱空間後使用如下程式碼

        private void Test1()
{
    try
    {
        WebClient client = new WebClient();
        NetworkCredential cred = new NetworkCredential("username", "password", "172.16.0.222");
        client.Credentials = cred;
        client.DownloadFile("file://172.16.0.222/test/111.txt", "111.txt");
    }
    catch (Exception ex)
    {
        // 如果網路很慢,而檔案又很大,這時可能有超時異常(Time out)。
    }
}

public void Test2()
{
    try
    {
        WebClient client = new WebClient();
        NetworkCredential cred = new NetworkCredential("username", "password", "domain");
        client.Credentials = cred;
        client.DownloadFile("file://172.16.0.222/test/111.txt", "111.txt");
    }
    catch (Exception ex)
    {
        // 如果網路很慢,而檔案又很大,這時可能有超時異常(Time out)。
    }
}


類似的還可以試試WebRequest、FileWebRequest等:

                WebRequest req = WebRequest.Create("file://138.12.12.14/generals/test.htm");
NetworkCredential cred = new NetworkCredential("username", "password", "IP");
req.Credentials = cred;
                WebResponse response = req.GetResponse();
Stream strm = response.GetResponseStream();
StreamReader r = new StreamReader(strm);
                ... ...

四、角色模擬

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.IO;

namespace Test
{
    public class Test
    {
        // logon types
        const int LOGON32_LOGON_INTERACTIVE = 2;
        const int LOGON32_LOGON_NETWORK = 3;
        const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
        // logon providers
        const int LOGON32_PROVIDER_DEFAULT = 0;
        const int LOGON32_PROVIDER_WINNT50 = 3;
        const int LOGON32_PROVIDER_WINNT40 = 2;
        const int LOGON32_PROVIDER_WINNT35 = 1;

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int LogonUser(String lpszUserName,
            String lpszDomain,
            String lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int DuplicateToken(IntPtr hToken,
            int impersonationLevel,
            ref IntPtr hNewToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool RevertToSelf();

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

        private WindowsImpersonationContext impersonationContext;

        public bool impersonateValidUser(String userName, String domain, String password)
        {
            WindowsIdentity tempWindowsIdentity;
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;

            if (RevertToSelf())
            {
                // 這裡使用LOGON32_LOGON_NEW_CREDENTIALS來訪問遠端資源。
                // 如果要(通過模擬使用者獲得許可權)實現伺服器程式,訪問本地授權資料庫可
                // 以用LOGON32_LOGON_INTERACTIVE
                if (LogonUser(userName, domain, password, LOGON32_LOGON_NEW_CREDENTIALS,
                    LOGON32_PROVIDER_DEFAULT, ref token) != 0)
                {
                    if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        impersonationContext = tempWindowsIdentity.Impersonate();
                        if (impersonationContext != null)
                        {
                            System.AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
                            IPrincipal pr = System.Threading.Thread.CurrentPrincipal;
                            IIdentity id = pr.Identity;
                            CloseHandle(token);
                            CloseHandle(tokenDuplicate);
                            return true;
                        }
                    }
                }
            }

            if (token != IntPtr.Zero)
                CloseHandle(token);

            if (tokenDuplicate != IntPtr.Zero)
                CloseHandle(tokenDuplicate);

            return false;
        }

        public void undoImpersonation()
        {
            impersonationContext.Undo();
        }

        public void TestFunc()
        {
            bool isImpersonated = false;
            try
            {
                if (impersonateValidUser("UserName", "Domain", "Password"))
                {
                    isImpersonated = true;
                    //do what you want now, as the special user
                    // ...
                    File.Copy(@"\\192.168.1.48\generals\now.htm", "c:\\now.htm", true);
                }
            }
            finally
            {
                if (isImpersonated)
                    undoImpersonation();
            }
        }
    }
}

五、比較

方法一通過呼叫Shell命令Net Use實現,有點笨拙。
方法二和方法一有些相似之處。對映遠端資源,然後訪問。
方法三由於會有超時異常出現,所以在網路速度快、傳輸小檔案時是可以的。
方法四通過身份模擬實現遠端資源訪問。一些伺服器程序就是通過這種方式執行的。這種方法也是我的最愛。

六、要注意的地方

關於這幾種方法,google後都可以找到一些文章。但是等到自己實際測試時,有時會出現各種小錯誤,
這些錯誤基本來源於兩方面:

1、函式的引數選擇有問題,和自己的環境不相符。
比如

public static extern int LogonUser(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);

中的dwLogonType,要訪問遠端資源就要用LOGON32_LOGON_NEW_CREDENTIALS,
要模擬本機使用者就要用LOGON32_LOGON_INTERACTIVE。

2、函式的引數格式有問題。

比如

public static extern int LogonUser(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);

中的lpszUserName、lpszDomain、lpszPassword就要寫清楚。

我就在這遇到過問題,第一次測試時,遠端伺服器就是一臺獨立的檔案伺服器,這是我的呼叫方式:
LogonUser(“myname”, “192.168.1.48”, “password”, LOGON32_LOGON_NEW_CREDENTIALS,
LOGON32_PROVIDER_DEFAULT, ref token);

第二次測試時,遠端伺服器是域MyDomain中的一個成員伺服器,提供檔案服務。這時程式碼就應該是:
LogonUser(“myname”, “MyDomain”, “password”, LOGON32_LOGON_NEW_CREDENTIALS,
LOGON32_PROVIDER_DEFAULT, ref token);

注意,程式碼中是MyDomain而不是IP地址。

再如:

參考上面程式碼

string remote = @"\\192.168.1.48\generals";
string local = @"P:";
string username = @"Domain\UserName";
string password = @"Password";

如果@"\192.168.1.48\generals"變成@"\192.168.1.48\generals\”就會出錯;
如果是域中的使用者,那麼把@"Domain\UserName"變成@"UserName"就會出錯。

//原文連結http://www.cnblogs.com/h2appy/archive/2008/05/21/1204277.html