从非受信任的远程域使用凭据访问共享文件(UNC)

158

我们遇到了一个有趣的问题需要解决,我的搜索结果没有任何发现。因此,我向 SO 社区寻求帮助。

问题是:我们需要以编程方式访问一个共享文件,该文件不在我们的域中,也不通过远程文件共享/ UNC 在受信任的外部域中。自然而然地,我们需要向远程计算机提供凭据。

通常情况下,有两种方法来解决这个问题:

  1. 将文件共享映射为驱动器,并在该时刻提供凭据。通常使用 NET USE 命令或复制 NET USE 的 Win32 函数来完成此操作。
  2. 像远程计算机在域中一样使用 UNC 路径访问文件,并确保程序运行的帐户被复制(包括密码)到远程计算机上作为本地用户。基本上利用 Windows 会在用户尝试访问共享文件时自动提供当前用户凭据的事实。
  3. 不使用远程文件共享。使用 FTP(或其他方式)传输文件,在本地进行操作,然后再传输回来。

由于各种原因,我们的安全/网络架构师拒绝了前两种方法。第二种方法显然存在安全漏洞;如果远程计算机被攻击,本地计算机现在就处于风险之中。第一种方法不令人满意,因为新挂载的驱动器在程序访问文件期间是其他程序可以访问的共享资源。即使这是可临时实现的,他们仍认为这是一个漏洞。

他们可以接受第三个选项,但远程网络管理员坚持使用 SFTP 而不是 FTPS,而 FtpWebRequest 只支持 FTPS。SFTP 是更友好的防火墙选项,我可以使用几个库来实现这个方法,但如果可能的话,我更愿意减少我的依赖项。

我已经在 MSDN 上搜索了使用远程文件共享的受管理或 Win32 方法,但我没有找到任何有用的信息。

因此,我问:是否还有其他方法?我错过了一个可以实现我想要的功能的超级机密win32函数吗?或者我必须追求选项3的某个变体?


我曾经看到过使用JScape工具相对来说比较简单地实现了选项3。你可以试一试。它不是免费的,但它确实能够完成它的工作。 - DreamSonic
我已经使用模拟身份的方法解决了这个问题,但是这是在域外的两台机器之间进行的。我不知道从域到域外的计算机通信是否会有问题。https://dev59.com/NHPYa4cB1Zd3GeqPkY9O - Wolf5
9个回答

187
解决问题的方法是使用名为WNetUseConnection的Win32 API。
使用此函数连接到具有身份验证的UNC路径,而不是映射驱动器。 这将允许您连接到远程计算机,即使它不在同一个域中,甚至如果它具有不同的用户名和密码。
一旦您使用了WNetUseConnection,您将能够通过UNC路径访问文件,就像您在同一个域中一样。最好的方式可能是通过内置的管理共享。
示例:\\计算机名\c$\program files\Folder\file.txt
以下是使用WNetUseConnection的一些C#代码示例。
请注意,对于NetResource,应将lpLocalName和lpProvider传递为null。dwType应为RESOURCETYPE_DISK。lpRemoteName应为\\计算机名。
using System;
using System.Runtime.InteropServices ;
using System.Threading;

namespace ExtremeMirror
{
    public class PinvokeWindowsNetworking
    {
        #region Consts
        const int RESOURCE_CONNECTED = 0x00000001;
        const int RESOURCE_GLOBALNET = 0x00000002;
        const int RESOURCE_REMEMBERED = 0x00000003;

        const int RESOURCETYPE_ANY = 0x00000000;
        const int RESOURCETYPE_DISK = 0x00000001;
        const int RESOURCETYPE_PRINT = 0x00000002;

        const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
        const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
        const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
        const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
        const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
        const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;

        const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
        const int RESOURCEUSAGE_CONTAINER = 0x00000002;


        const int CONNECT_INTERACTIVE = 0x00000008;
        const int CONNECT_PROMPT = 0x00000010;
        const int CONNECT_REDIRECT = 0x00000080;
        const int CONNECT_UPDATE_PROFILE = 0x00000001;
        const int CONNECT_COMMANDLINE = 0x00000800;
        const int CONNECT_CMD_SAVECRED = 0x00001000;

        const int CONNECT_LOCALDRIVE = 0x00000100;
        #endregion

        #region Errors
        const int NO_ERROR = 0;

        const int ERROR_ACCESS_DENIED = 5;
        const int ERROR_ALREADY_ASSIGNED = 85;
        const int ERROR_BAD_DEVICE = 1200;
        const int ERROR_BAD_NET_NAME = 67;
        const int ERROR_BAD_PROVIDER = 1204;
        const int ERROR_CANCELLED = 1223;
        const int ERROR_EXTENDED_ERROR = 1208;
        const int ERROR_INVALID_ADDRESS = 487;
        const int ERROR_INVALID_PARAMETER = 87;
        const int ERROR_INVALID_PASSWORD = 1216;
        const int ERROR_MORE_DATA = 234;
        const int ERROR_NO_MORE_ITEMS = 259;
        const int ERROR_NO_NET_OR_BAD_PATH = 1203;
        const int ERROR_NO_NETWORK = 1222;

        const int ERROR_BAD_PROFILE = 1206;
        const int ERROR_CANNOT_OPEN_PROFILE = 1205;
        const int ERROR_DEVICE_IN_USE = 2404;
        const int ERROR_NOT_CONNECTED = 2250;
        const int ERROR_OPEN_FILES  = 2401;

        private struct ErrorClass 
        {
            public int num;
            public string message;
            public ErrorClass(int num, string message) 
            {
                this.num = num;
                this.message = message;
            }
        }


        // Created with excel formula:
        // ="new ErrorClass("&A1&", """&PROPER(SUBSTITUTE(MID(A1,7,LEN(A1)-6), "_", " "))&"""), "
        private static ErrorClass[] ERROR_LIST = new ErrorClass[] {
            new ErrorClass(ERROR_ACCESS_DENIED, "Error: Access Denied"), 
            new ErrorClass(ERROR_ALREADY_ASSIGNED, "Error: Already Assigned"), 
            new ErrorClass(ERROR_BAD_DEVICE, "Error: Bad Device"), 
            new ErrorClass(ERROR_BAD_NET_NAME, "Error: Bad Net Name"), 
            new ErrorClass(ERROR_BAD_PROVIDER, "Error: Bad Provider"), 
            new ErrorClass(ERROR_CANCELLED, "Error: Cancelled"), 
            new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"), 
            new ErrorClass(ERROR_INVALID_ADDRESS, "Error: Invalid Address"), 
            new ErrorClass(ERROR_INVALID_PARAMETER, "Error: Invalid Parameter"), 
            new ErrorClass(ERROR_INVALID_PASSWORD, "Error: Invalid Password"), 
            new ErrorClass(ERROR_MORE_DATA, "Error: More Data"), 
            new ErrorClass(ERROR_NO_MORE_ITEMS, "Error: No More Items"), 
            new ErrorClass(ERROR_NO_NET_OR_BAD_PATH, "Error: No Net Or Bad Path"), 
            new ErrorClass(ERROR_NO_NETWORK, "Error: No Network"), 
            new ErrorClass(ERROR_BAD_PROFILE, "Error: Bad Profile"), 
            new ErrorClass(ERROR_CANNOT_OPEN_PROFILE, "Error: Cannot Open Profile"), 
            new ErrorClass(ERROR_DEVICE_IN_USE, "Error: Device In Use"), 
            new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"), 
            new ErrorClass(ERROR_NOT_CONNECTED, "Error: Not Connected"), 
            new ErrorClass(ERROR_OPEN_FILES, "Error: Open Files"), 
        };

        private static string getErrorForNumber(int errNum) 
        {
            foreach (ErrorClass er in ERROR_LIST) 
            {
                if (er.num == errNum) return er.message;
            }
            return "Error: Unknown, " + errNum;
        }
        #endregion

        [DllImport("Mpr.dll")] private static extern int WNetUseConnection(
            IntPtr hwndOwner,
            NETRESOURCE lpNetResource,
            string lpPassword,
            string lpUserID,
            int dwFlags,
            string lpAccessName,
            string lpBufferSize,
            string lpResult
        );

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

        [StructLayout(LayoutKind.Sequential)] private class NETRESOURCE
        { 
            public int dwScope = 0;
            public int dwType = 0;
            public int dwDisplayType = 0;
            public int dwUsage = 0;
            public string lpLocalName = "";
            public string lpRemoteName = "";
            public string lpComment = "";
            public string lpProvider = "";
        }


        public static string connectToRemote(string remoteUNC, string username, string password) 
        {
            return connectToRemote(remoteUNC, username, password, false);
        }

        public static string connectToRemote(string remoteUNC, string username, string password, bool promptUser) 
        {
            NETRESOURCE nr = new NETRESOURCE();
            nr.dwType = RESOURCETYPE_DISK;
            nr.lpRemoteName = remoteUNC;
            //          nr.lpLocalName = "F:";

            int ret;
            if (promptUser) 
                ret = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null);
            else 
                ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);

            if (ret == NO_ERROR) return null;
            return getErrorForNumber(ret);
        }

        public static string disconnectRemote(string remoteUNC) 
        {
            int ret = WNetCancelConnection2(remoteUNC, CONNECT_UPDATE_PROFILE, false);
            if (ret == NO_ERROR) return null;
            return getErrorForNumber(ret);
        }
    }
}

仅限于未设置用户名或密码的计算机,才可不进行连接。而断开连接是可以的。甚至可以通过命令行来实现。 - Brian R. Bondy
1
嗨,Brian。你链接的文档说可以传递NULL作为用户名和密码来使用当前凭据。我会进行一些测试以查看是否有效。 - flipdoubt
使用 WNetUseConnection 打开的连接是否需要手动关闭,通过调用 WNetCancelConnection2 来关闭?还是有空闲超时(或其他机制),我们不必担心? - w128
有时候你不得不往下滚动才能找到一个好的解决方案! - Kentonbmax
请问您能否提供一些关于使用这种方法在Windows机器之间安全传输文件的信息? - m00sila
显示剩余5条评论

142

对于寻求快速解决方案的用户,您可以使用我最近编写的NetworkShareAccesser(基于这个答案(非常感谢!)):

用法:

using (NetworkShareAccesser.Access(REMOTE_COMPUTER_NAME, DOMAIN, USER_NAME, PASSWORD))
{
    File.Copy(@"C:\Some\File\To\copy.txt", @"\\REMOTE-COMPUTER\My\Shared\Target\file.txt");
}

警告:请务必确保调用NetworkShareAccesserDispose方法被调用(即使您的应用程序崩溃!),否则将在Windows上保留一个开放的连接。您可以通过打开cmd提示符并输入net use来查看所有打开的连接。

代码:

/// <summary>
/// Provides access to a network share.
/// </summary>
public class NetworkShareAccesser : IDisposable
{
    private string _remoteUncName;
    private string _remoteComputerName;

    public string RemoteComputerName
    {
        get
        {
            return this._remoteComputerName;
        }
        set
        {
            this._remoteComputerName = value;
            this._remoteUncName = @"\\" + this._remoteComputerName;
        }
    }

    public string UserName
    {
        get;
        set;
    }
    public string Password
    {
        get;
        set;
    }

    #region Consts

    private const int RESOURCE_CONNECTED = 0x00000001;
    private const int RESOURCE_GLOBALNET = 0x00000002;
    private const int RESOURCE_REMEMBERED = 0x00000003;

    private const int RESOURCETYPE_ANY = 0x00000000;
    private const int RESOURCETYPE_DISK = 0x00000001;
    private const int RESOURCETYPE_PRINT = 0x00000002;

    private const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
    private const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
    private const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
    private const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
    private const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
    private const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;

    private const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
    private const int RESOURCEUSAGE_CONTAINER = 0x00000002;


    private const int CONNECT_INTERACTIVE = 0x00000008;
    private const int CONNECT_PROMPT = 0x00000010;
    private const int CONNECT_REDIRECT = 0x00000080;
    private const int CONNECT_UPDATE_PROFILE = 0x00000001;
    private const int CONNECT_COMMANDLINE = 0x00000800;
    private const int CONNECT_CMD_SAVECRED = 0x00001000;

    private const int CONNECT_LOCALDRIVE = 0x00000100;

    #endregion

    #region Errors

    private const int NO_ERROR = 0;

    private const int ERROR_ACCESS_DENIED = 5;
    private const int ERROR_ALREADY_ASSIGNED = 85;
    private const int ERROR_BAD_DEVICE = 1200;
    private const int ERROR_BAD_NET_NAME = 67;
    private const int ERROR_BAD_PROVIDER = 1204;
    private const int ERROR_CANCELLED = 1223;
    private const int ERROR_EXTENDED_ERROR = 1208;
    private const int ERROR_INVALID_ADDRESS = 487;
    private const int ERROR_INVALID_PARAMETER = 87;
    private const int ERROR_INVALID_PASSWORD = 1216;
    private const int ERROR_MORE_DATA = 234;
    private const int ERROR_NO_MORE_ITEMS = 259;
    private const int ERROR_NO_NET_OR_BAD_PATH = 1203;
    private const int ERROR_NO_NETWORK = 1222;

    private const int ERROR_BAD_PROFILE = 1206;
    private const int ERROR_CANNOT_OPEN_PROFILE = 1205;
    private const int ERROR_DEVICE_IN_USE = 2404;
    private const int ERROR_NOT_CONNECTED = 2250;
    private const int ERROR_OPEN_FILES = 2401;

    #endregion

    #region PInvoke Signatures

    [DllImport("Mpr.dll")]
    private static extern int WNetUseConnection(
        IntPtr hwndOwner,
        NETRESOURCE lpNetResource,
        string lpPassword,
        string lpUserID,
        int dwFlags,
        string lpAccessName,
        string lpBufferSize,
        string lpResult
        );

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

    [StructLayout(LayoutKind.Sequential)]
    private class NETRESOURCE
    {
        public int dwScope = 0;
        public int dwType = 0;
        public int dwDisplayType = 0;
        public int dwUsage = 0;
        public string lpLocalName = "";
        public string lpRemoteName = "";
        public string lpComment = "";
        public string lpProvider = "";
    }

    #endregion

    /// <summary>
    /// Creates a NetworkShareAccesser for the given computer name. The user will be promted to enter credentials
    /// </summary>
    /// <param name="remoteComputerName"></param>
    /// <returns></returns>
    public static NetworkShareAccesser Access(string remoteComputerName)
    {
        return new NetworkShareAccesser(remoteComputerName);
    }

    /// <summary>
    /// Creates a NetworkShareAccesser for the given computer name using the given domain/computer name, username and password
    /// </summary>
    /// <param name="remoteComputerName"></param>
    /// <param name="domainOrComuterName"></param>
    /// <param name="userName"></param>
    /// <param name="password"></param>
    public static NetworkShareAccesser Access(string remoteComputerName, string domainOrComuterName, string userName, string password)
    {
        return new NetworkShareAccesser(remoteComputerName,
                                        domainOrComuterName + @"\" + userName,
                                        password);
    }

    /// <summary>
    /// Creates a NetworkShareAccesser for the given computer name using the given username (format: domainOrComputername\Username) and password
    /// </summary>
    /// <param name="remoteComputerName"></param>
    /// <param name="userName"></param>
    /// <param name="password"></param>
    public static NetworkShareAccesser Access(string remoteComputerName, string userName, string password)
    {
        return new NetworkShareAccesser(remoteComputerName, 
                                        userName,
                                        password);
    }

    private NetworkShareAccesser(string remoteComputerName)
    {
        RemoteComputerName = remoteComputerName;               

        this.ConnectToShare(this._remoteUncName, null, null, true);
    }

    private NetworkShareAccesser(string remoteComputerName, string userName, string password)
    {
        RemoteComputerName = remoteComputerName;
        UserName = userName;
        Password = password;

        this.ConnectToShare(this._remoteUncName, this.UserName, this.Password, false);
    }

    private void ConnectToShare(string remoteUnc, string username, string password, bool promptUser)
    {
        NETRESOURCE nr = new NETRESOURCE
        {
            dwType = RESOURCETYPE_DISK,
            lpRemoteName = remoteUnc
        };

        int result;
        if (promptUser)
        {
            result = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null);
        }
        else
        {
            result = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);
        }

        if (result != NO_ERROR)
        {
            throw new Win32Exception(result);
        }
    }

    private void DisconnectFromShare(string remoteUnc)
    {
        int result = WNetCancelConnection2(remoteUnc, CONNECT_UPDATE_PROFILE, false);
        if (result != NO_ERROR)
        {
            throw new Win32Exception(result);
        }
    }

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    /// <filterpriority>2</filterpriority>
    public void Dispose()
    {
        this.DisconnectFromShare(this._remoteUncName);
    }
}

5
你还需要使用 System.Runtime.InteropServicesSystem.ComponentModel 命名空间来进行 DllImportWin32Exception 操作。 - Kᴀτᴢ
2
我正在尝试在远程计算机上使用您的解决方案与本地用户帐户,但我一直收到访问被拒绝的错误。您的解决方案只适用于网络帐户吗? - M3NTA7
1
该账户存在于远程机器上,但它不是网络账户,而是本地机器账户。我已经尝试将域设置为机器名称。我还在共享文件夹上给了本地用户账户完全权限,但我仍然被拒绝访问。您有任何想法为什么会发生这种情况吗?谢谢。 - M3NTA7
2
注意:释放对象似乎并不会从系统中清除凭据(Windows 10);在连接被“取消”后,我仍然能够访问远程计算机上的文件。重新登录我的用户帐户或重新启动计算机似乎可以清除此内部缓存。 - user142162
1
这个解决方案对我无效,以UNC路径访问我的NAS需要凭据。Brian R. Bondy的解决方案一次成功。以防其他人首先尝试此解决方案,但它对他们不起作用。 - deadlydog
显示剩余10条评论

16

据我所知,为了为服务器建立凭据,您不需要将UNC路径映射到驱动器号。我经常使用像这样的批处理脚本:

net use \\myserver /user:username password

:: do something with \\myserver\the\file\i\want.xml

net use /delete \\my.server.com

然而,与你的程序运行在同一账户下的任何程序仍然能够访问username:password所拥有的所有内容。一种可能的解决方案是将你的程序隔离在自己的本地用户帐户中(UNC 访问仅限于调用了NET USE的帐户)。

注意:在跨域上使用 SMB 不是技术的最佳应用,我个人认为。如果安全性很重要,那么 SMB 缺乏加密功能本身就会有点打击。


如果您对UNC访问仅限于调用“NET USE”的帐户的正确性有把握,那么这可能是可行的方法。然而,您确定我们需要使用本地帐户吗?NET USE调用不会仅限于调用它的计算机吗?您给了我一个很好的研究路径。 - Randolpho
据我所知,可能我错了,UNC访问仅对NET USE调用所使用的特定安全主体(SAM帐户等)可用。您可以通过使用RunAs映射路径,然后尝试从另一个帐户访问它来验证这一点。 - Jacob
在我的情况下,我不得不使用以下命令: net use \myserver /user:username@domain password 因为用户位于另一个域中。 - StarCub

5
这是一个最小化的POC类,已经删除了所有不必要的内容。
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

public class UncShareWithCredentials : IDisposable
{
    private string _uncShare;

    public UncShareWithCredentials(string uncShare, string userName, string password)
    {
        var nr = new Native.NETRESOURCE
        {
            dwType = Native.RESOURCETYPE_DISK,
            lpRemoteName = uncShare
        };

        int result = Native.WNetUseConnection(IntPtr.Zero, nr, password, userName, 0, null, null, null);
        if (result != Native.NO_ERROR)
        {
            throw new Win32Exception(result);
        }
        _uncShare = uncShare;
    }

    public void Dispose()
    {
        if (!string.IsNullOrEmpty(_uncShare))
        {
            Native.WNetCancelConnection2(_uncShare, Native.CONNECT_UPDATE_PROFILE, false);
            _uncShare = null;
        }
    }

    private class Native
    {
        public const int RESOURCETYPE_DISK = 0x00000001;
        public const int CONNECT_UPDATE_PROFILE = 0x00000001;
        public const int NO_ERROR = 0;

        [DllImport("mpr.dll")]
        public static extern int WNetUseConnection(IntPtr hwndOwner, NETRESOURCE lpNetResource, string lpPassword, string lpUserID,
            int dwFlags, string lpAccessName, string lpBufferSize, string lpResult);

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

        [StructLayout(LayoutKind.Sequential)]
        public class NETRESOURCE
        {
            public int dwScope;
            public int dwType;
            public int dwDisplayType;
            public int dwUsage;
            public string lpLocalName;
            public string lpRemoteName;
            public string lpComment;
            public string lpProvider;
        }
    }
}

你可以直接使用 \\server\share\folderWNetUseConnection,无需提前将其剥离为仅包含 \\server 的部分。

4

虽然我自己不确定,但我希望第二点是错误的...我认为Windows不会自动向任何机器(尤其是不属于我的信任范围)提供我的登录信息(更不用说密码了!)。

无论如何,你是否探索过模拟架构?你的代码将类似于以下内容:

using (System.Security.Principal.WindowsImpersonationContext context = System.Security.Principal.WindowsIdentity.Impersonate(token))
{
    // Do network operations here

    context.Undo();
}

在这种情况下,token变量是一个IntPtr。为了获得此变量的值,您需要调用非托管的LogonUser Windows API函数。快速访问pinvoke.net,可以得到以下签名:
[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
    string lpszUsername,
    string lpszDomain,
    string lpszPassword,
    int dwLogonType,
    int dwLogonProvider,
    out IntPtr phToken
);

用户名,域名和密码应该很明显。请查看可以传递给dwLogonType和dwLogonProvider的各种值,以确定最适合您需求的值。

由于我这里没有第二个域来验证,所以这段代码尚未经过测试,但希望它能为您指明正确的方向。


7
当你尝试使用来自不受信任域的登录ID时,冒充是行不通的。用户ID必须能够在本地登录。 - Moose
是的,我们尝试过这种方法,结果就像@Moose所说的那样:该域不受信任,因此无法进行模拟。 - Randolpho
是的,一旦我看到了那个评论,这就是为什么我使用NetUseAdd发布了答案(它与WNetUseConnection和WNetAddConnection函数之间的主要区别在于NetUseAdd不会使连接在Windows资源管理器中可见)。 - Adam Robinson
模拟登录在同一域上无法正常工作,我的测试结果是使用管理员帐户(两台计算机上的管理员)尝试读取共享文件夹时仍然会返回“访问被拒绝”的错误。因此,我认为这不是正确的方法。 - lidermin

4
我建议使用NetUseAdd而不是WNetUseConnection。WNetUseConnection是一个已经被WNetUseConnection2和WNetUseConnection3取代的传统函数,但所有这些函数都会创建一个在Windows资源管理器中可见的网络设备。NetUseAdd等同于在DOS提示符中调用“net use”以在远程计算机上进行身份验证。
如果调用NetUseAdd,则随后尝试访问目录应该成功。

1
@Adam Robinson:这并不正确。WNetUseConnection2和WNetUseConnection3并不存在。我认为你想到的是WNetAddConnection被WNetAddConnection2和WnetAddConnection3取代的情况。而且,你所提供的信息也是错误的。 - Brian R. Bondy
WNetUseConnection类似于WNetAddConnection3,但是它还可以选择性地创建映射到本地驱动器的能力。你不一定非要使用。 - Brian R. Bondy
@BrianR.Bondy,它们确实存在,只是没有用C#实现。来源:https://learn.microsoft.com/da-dk/windows/win32/api/lmuse/nf-lmuse-netuseadd?redirectedfrom=MSDN 引用:“您还可以使用WNetAddConnection2和WNetAddConnection3函数将本地设备重定向到网络资源。” - Thomas Williams

2
大多数SFTP服务器也支持SCP,这样可以更容易地找到库。您甚至可以从您的代码中调用现有的客户端,如PuTTY中包含的pscp。
如果您正在处理的文件类型比较简单,比如文本或XML文件,您甚至可以编写自己的客户端/服务器实现,使用像.NET Remoting或Web服务这样的东西来操作该文件。

1

我附上了基于brian参考的VB.NET代码

Imports System.ComponentModel

Imports System.Runtime.InteropServices

Public Class PinvokeWindowsNetworking

Const NO_ERROR As Integer = 0



Private Structure ErrorClass

    Public num As Integer

    Public message As String



    Public Sub New(ByVal num As Integer, ByVal message As String)

        Me.num = num

        Me.message = message

    End Sub

End Structure



Private Shared ERROR_LIST As ErrorClass() = New ErrorClass() {

    New ErrorClass(5, "Error: Access Denied"),

    New ErrorClass(85, "Error: Already Assigned"),

    New ErrorClass(1200, "Error: Bad Device"),

    New ErrorClass(67, "Error: Bad Net Name"),

    New ErrorClass(1204, "Error: Bad Provider"),

    New ErrorClass(1223, "Error: Cancelled"),

    New ErrorClass(1208, "Error: Extended Error"),

    New ErrorClass(487, "Error: Invalid Address"),

    New ErrorClass(87, "Error: Invalid Parameter"),

    New ErrorClass(1216, "Error: Invalid Password"),

    New ErrorClass(234, "Error: More Data"),

    New ErrorClass(259, "Error: No More Items"),

    New ErrorClass(1203, "Error: No Net Or Bad Path"),

    New ErrorClass(1222, "Error: No Network"),

    New ErrorClass(1206, "Error: Bad Profile"),

    New ErrorClass(1205, "Error: Cannot Open Profile"),

    New ErrorClass(2404, "Error: Device In Use"),

    New ErrorClass(2250, "Error: Not Connected"),

    New ErrorClass(2401, "Error: Open Files")}



Private Shared Function getErrorForNumber(ByVal errNum As Integer) As String

    For Each er As ErrorClass In ERROR_LIST

        If er.num = errNum Then Return er.message

    Next



    Try

        Throw New Win32Exception(errNum)

    Catch ex As Exception

        Return "Error: Unknown, " & errNum & " " & ex.Message

    End Try



    Return "Error: Unknown, " & errNum

End Function



<DllImport("Mpr.dll")>

Private Shared Function WNetUseConnection(ByVal hwndOwner As IntPtr, ByVal lpNetResource As NETRESOURCE, ByVal lpPassword As String, ByVal lpUserID As String, ByVal dwFlags As Integer, ByVal lpAccessName As String, ByVal lpBufferSize As String, ByVal lpResult As String) As Integer

End Function



<DllImport("Mpr.dll")>

Private Shared Function WNetCancelConnection2(ByVal lpName As String, ByVal dwFlags As Integer, ByVal fForce As Boolean) As Integer

End Function



<StructLayout(LayoutKind.Sequential)>

Private Class NETRESOURCE

    Public dwScope As Integer = 0

    Public dwType As Integer = 0

    Public dwDisplayType As Integer = 0

    Public dwUsage As Integer = 0

    Public lpLocalName As String = ""

    Public lpRemoteName As String = ""

    Public lpComment As String = ""

    Public lpProvider As String = ""

End Class



Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String) As String

    Return connectToRemote(remoteUNC, username, password, False)

End Function



Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String, ByVal promptUser As Boolean) As String

    Dim nr As NETRESOURCE = New NETRESOURCE()

    nr.dwType = ResourceTypes.Disk

    nr.lpRemoteName = remoteUNC

    Dim ret As Integer



    If promptUser Then

        ret = WNetUseConnection(IntPtr.Zero, nr, "", "", Connects.Interactive Or Connects.Prompt, Nothing, Nothing, Nothing)

    Else

        ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, Nothing, Nothing, Nothing)

    End If



    If ret = NO_ERROR Then Return Nothing

    Return getErrorForNumber(ret)

End Function



Public Shared Function disconnectRemote(ByVal remoteUNC As String) As String

    Dim ret As Integer = WNetCancelConnection2(remoteUNC, Connects.UpdateProfile, False)

    If ret = NO_ERROR Then Return Nothing

    Return getErrorForNumber(ret)

End Function


Enum Resources As Integer

    Connected = &H1

    GlobalNet = &H2

    Remembered = &H3

End Enum


Enum ResourceTypes As Integer

    Any = &H0

    Disk = &H1

    Print = &H2

End Enum


Enum ResourceDisplayTypes As Integer

    Generic = &H0

    Domain = &H1

    Server = &H2

    Share = &H3

    File = &H4

    Group = &H5

End Enum


Enum ResourceUsages As Integer

    Connectable = &H1

    Container = &H2

End Enum


Enum Connects As Integer

    Interactive = &H8

    Prompt = &H10

    Redirect = &H80

    UpdateProfile = &H1

    CommandLine = &H800

    CmdSaveCred = &H1000

    LocalDrive = &H100

End Enum


End Class

如何使用它
Dim login = PinvokeWindowsNetworking.connectToRemote("\\ComputerName", "ComputerName\UserName", "Password")

    If IsNothing(login) Then



        'do your thing on the shared folder



       PinvokeWindowsNetworking.disconnectRemote("\\ComputerName")

    End If

-2

我查阅了微软的资料以寻找答案。第一个解决方案假设运行应用程序进程的用户帐户可以访问共享文件夹或驱动器(同一域)。确保您的 DNS 已解析或尝试使用 IP 地址。只需执行以下操作:

 DirectoryInfo di = new DirectoryInfo(PATH);
 var files = di.EnumerateFiles("*.*", SearchOption.AllDirectories);

如果您想在不同的域中使用.NET 2.0凭据,请按照以下模型操作:
WebRequest req = FileWebRequest.Create(new Uri(@"\\<server Name>\Dir\test.txt"));

        req.Credentials = new NetworkCredential(@"<Domain>\<User>", "<Password>");
        req.PreAuthenticate = true;

        WebResponse d = req.GetResponse();
        FileStream fs = File.Create("test.txt");

        // here you can check that the cast was successful if you want. 
        fs = d.GetResponseStream() as FileStream;
        fs.Close();

看起来很有趣。 - DeerSpotter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接