158

We've run into an interesting situation that needs solving, and my searches have turned up nill. I therefore appeal to the SO community for help.

The issue is this: we have a need to programmatically access a shared file that is not in our domain, and is not within a trusted external domain via remote file sharing / UNC. Naturally, we need to supply credentials to the remote machine.

Typically, one solves this problem in one of two ways:

  1. Map the file share as a drive and supply the credentials at that time. This is typically done using the NET USE command or the Win32 functions that duplicate NET USE.
  2. Access the file with a UNC path as if the remote computer were on the domain and ensure that the account under which the program runs is duplicated (including password) on the remote machine as a local user. Basically leverage the fact that Windows will automatically supply the current user's credentials when the user attempts to access a shared file.
  3. Don't use remote file sharing. Use FTP (or some other means) to transfer the file, work on it locally, then transfer it back.

For various and sundry reasons, our security / network architects have rejected the first two approaches. The second approach is obviously a security hole; if the remote computer is compromised, the local computer is now at risk. The first approach is unsatisfactory because the newly mounted drive is a shared resource available to other programs on the local computer during file access by the program. Even though it's quite possible to make this temporary, it's still a hole in their opinion.

They're open to the third option, but the remote network admins insist on SFTP rather than FTPS, and FtpWebRequest only supports FTPS. SFTP is the more firewall-friendly option and there are a couple libraries I could use for that approach, but I'd prefer to reduce my dependencies if I can.

I've searched MSDN for either a managed or a win32 means of using remote file sharing, but I have failed to come up with anything useful.

And so I ask: Is there another way? Did I miss a super-secret win32 function that does what I want? Or must I pursue some variant of option 3?

Randolpho
  • 55,384
  • 17
  • 145
  • 179
  • I have solved it with the impersonation approach, but that is between 2 machines outside a domain. I do not know if it wouldhave an issue talking from a domain to a computer outside the domain. http://stackoverflow.com/questions/17221476/c-sharp-programmatic-remote-folder-file-authentication-in-non-domain-windows-e – Wolf5 Jul 25 '13 at 20:32
  • I've seen option 3 implemented with [JScape tools](http://www.jscape.com/sshfactorydotnet/) in a pretty straightforward fashion. You might give it a try. It's not free, but it does its job. – DreamSonic Mar 26 '09 at 00:41

9 Answers9

186

The way to solve your problem is to use a Win32 API called WNetUseConnection.
Use this function to connect to a UNC path with authentication, NOT to map a drive.

This will allow you to connect to a remote machine, even if it is not on the same domain, and even if it has a different username and password.

Once you have used WNetUseConnection you will be able to access the file via a UNC path as if you were on the same domain. The best way is probably through the administrative built in shares.
Example: \\computername\c$\program files\Folder\file.txt

Here is some sample C# code that uses WNetUseConnection.
Note, for the NetResource, you should pass null for the lpLocalName and lpProvider. The dwType should be RESOURCETYPE_DISK. The lpRemoteName should be \\ComputerName.

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);
        }
    }
}
Jpsy
  • 20,077
  • 7
  • 118
  • 115
Brian R. Bondy
  • 339,232
  • 124
  • 596
  • 636
  • Is there any way to use functions like these to explicitly open/close connections to a network machine using the current credentials, i.e., without providing the username and password? I am specifically interested in closing a connection after accessing a file share. – flipdoubt Oct 23 '09 at 12:44
  • Not for connecting, unless the computer itself doesn't have a username or password. For disconnecting sure you can. You can even do it via command line instead. – Brian R. Bondy Oct 23 '09 at 13:06
  • 1
    Hi Brian. The docs you link to say you can pass NULL for the user name and password to use the current credentials. I will do some testing to see if this works. – flipdoubt Oct 23 '09 at 19:27
  • Passing null for the user name/password allows me to connect, but how can I prove that I have disconnected? Is there something on the server I can look at? On Server 2003, I can watch the sessions, but the list of current sessions updates just as fast when my app does not use these APIs. – flipdoubt Oct 28 '09 at 11:55
  • Should connections opened with `WNetUseConnection` be closed manually by calling `WNetCancelConnection2`? Or is there an idle timeout (or some other mechanism) and we don't have to bother? – w128 Nov 26 '13 at 10:05
  • Sometimes you have to scroll to the bottom to find a great solution! – Kentonbmax Nov 09 '15 at 14:44
  • Could you please provide some information how secure using this method to transfer file between Windows machines? – m00sila Jun 20 '17 at 15:04
  • I tried a different solution using impersonation as decribed here: https://stackoverflow.com/a/38490877/342594 That didn't work, but the above code worked right away!. Thanks @BrianR.Bondy – Sascha Mar 20 '18 at 12:22
  • Thanks @BrianR.Bondy for sharing the code. By your solutions I am able to solve the issue of connecting to remote servers. I tried a different solution using impersonate but did not solve the issue. – Sabbu Mar 28 '22 at 19:03
  • Not work on IIS, say logon session not exists, could be terminated. – ahdung May 27 '22 at 08:56
141

For people looking for a quick solution, you can use the NetworkShareAccesser I wrote recently (based on this answer (thanks so much!)):

Usage:

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");
}

WARNING: Please make absolutely sure, that Dispose of the NetworkShareAccesser is called (even if you app crashes!), otherwise an open connection will remain on Windows. You can see all open connections by opening the cmd prompt and enter net use.

The Code:

/// <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);
    }
}
Community
  • 1
  • 1
GameScripting
  • 16,092
  • 13
  • 59
  • 98
  • 5
    you need also `using System.Runtime.InteropServices;` and `using System.ComponentModel;` for `DllImport` and `Win32Exception` – Kᴀτᴢ Apr 29 '16 at 10:41
  • 1
    This solution has stopped my long day search. Thanks!!! Works pretty good as required. – Venkatesh Aug 23 '16 at 09:15
  • Just the thing I needed. Thx – Sebastian S Dec 27 '16 at 12:00
  • 2
    I'm trying to use your solution with a local user account on the remote machine, but I keep getting an access denied error. Will your solution only work for network accounts? – M3NTA7 May 04 '17 at 13:45
  • The user account must exist on the remote machine. That's how Windows works. – GameScripting May 04 '17 at 13:54
  • 1
    The account exists on the remote machine, but it is not a network account. It's a local machine account. I have tried setting the domain to the name of the machine. I have also given full permissions to the local user account on the shared folder, but I get access denied. Any ideas on why this may be happening? thx. – M3NTA7 May 04 '17 at 16:02
  • It should be working then. Please open a new question if you have any further questions. – GameScripting May 04 '17 at 16:50
  • 2
    Note: disposing the object does not seem to wipe the credentials from the system (Windows 10); I am able to access files on the remote computer after the connection has been "cancelled". Re-logging in to my user account or restarting my computer seems to clear this internal cache. –  Oct 24 '17 at 12:15
  • @Tim There is a difference between a used network share you will see in net use and your credential cache. See https://serverfault.com/questions/451387/how-to-delete-cached-temporarily-credentials-for-a-network-share-on-a-windows-ma . You have to remove the Network Access Login because Windows would throw an exception if you forgot. Problems will happen when you change the password of the cached user. – Lord_Pinhead Aug 23 '19 at 09:01
  • What do I put in for username, password & domain when the shared folder is accessible to anyone? – Jesper Feb 25 '20 at 12:25
  • @Jesper even if everyone has access to the folder, you still need SOME user the access the files. Think: which user should appear in the audit-log when accessing the file? – GameScripting Feb 26 '20 at 08:49
  • @GameScripting Exactly, so if the remote folder is shared to "EveryOne" how do you connect to it? What credentials do you use? – Jesper Feb 26 '20 at 13:06
  • @Jesper you could create a user (on the pc / in active directory) "myuser" with password "mypassword" and use that user. You just need SOME "dummy" user. – GameScripting Feb 28 '20 at 09:13
  • @Jesper Just remember that Everyone means either a known user with valid password OR unknown user. If you provide to the server a username which is known but with wrong password it won't let you authenticate thus you won't be able to access the share. But if you provide some random username it will let you pass. – badsamaritan Aug 30 '20 at 16:21
  • 1
    This solution did not work for me to access my NAS via a UNC path which required credentials. Brian R. Bondy's solution worked first try though. Just in case others try this solution first, but it doesn't work for them. – deadlydog Jan 07 '22 at 01:38
16

AFAIK, you don't need to map the UNC path to a drive letter in order to establish credentials for a server. I regularly used batch scripts like:

net use \\myserver /user:username password

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

net use /delete \\my.server.com

However, any program running on the same account as your program would still be able to access everything that username:password has access to. A possible solution could be to isolate your program in its own local user account (the UNC access is local to the account that called NET USE).

Note: Using SMB accross domains is not quite a good use of the technology, IMO. If security is that important, the fact that SMB lacks encryption is a bit of a damper all by itself.

Jacob
  • 22,785
  • 8
  • 39
  • 55
  • If you're correct about the UNC access being only available to the account that called `NET USE`, that might be a viable approach. Are you certain we need to use a local account, however? Wouldn't the `NET USE` call be local to the machine on which it was called? You've given me a good research path – Randolpho Mar 26 '09 at 01:51
  • AFAIK, and I may be wrong, the UNC access will only be available to the specific security principal (SAM account, whatever) under which the call to NET USE was made. You can verify this by using RunAs to map the path and then trying to access it from another account. – Jacob Mar 27 '09 at 10:02
  • in my case, i had to use net use \\myserver /user:username@domain password as the user is on a different domain. – StarCub Sep 28 '10 at 02:04
5

Here a minimal POC class w/ all the cruft removed

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;
        }
    }
}

You can directly use \\server\share\folder w/ WNetUseConnection, no need to strip it to \\server part only beforehand.

wqw
  • 11,771
  • 1
  • 33
  • 41
4

While I don't know myself, I would certainly hope that #2 is incorrect...I'd like to think that Windows isn't going to AUTOMATICALLY give out my login information (least of all my password!) to any machine, let alone one that isn't part of my trust.

Regardless, have you explored the impersonation architecture? Your code is going to look similar to this:

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

    context.Undo();
}

In this case, the token variable is an IntPtr. In order to get a value for this variable, you'll have to call the unmanaged LogonUser Windows API function. A quick trip to pinvoke.net gives us the following signature:

[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
);

Username, domain, and password should seem fairly obvious. Have a look at the various values that can be passed to dwLogonType and dwLogonProvider to determine the one that best suits your needs.

This code hasn't been tested, as I don't have a second domain here where I can verify, but this should hopefully put you on the right track.

Kiquenet
  • 14,494
  • 35
  • 148
  • 243
Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
  • 7
    Impersonation won't work when you are trying to use a login id from an untrusted domain. The user id has to be able to log on locally. – Moose Mar 24 '09 at 20:01
  • Yeah, we tried this route, it ended up being as @Moose says: The domain is untrusted and therefore impersonation won't work. – Randolpho Mar 29 '09 at 15:39
  • Yeah, once I saw that comment that's why I posted the answer using NetUseAdd (the primary difference between it and the WNetUseConnection and WNetAddConnection functions being that NetUseAdd does not make the connection visible in Windows Explorer). – Adam Robinson Mar 29 '09 at 16:57
  • Impersonation neither works on the same domain, on my tests it keeps responding me with Access Denied trying to read a file on a shared folder with an administrator account (admin on both machines). So, I think this isn't the right approach. – lidermin Dec 08 '10 at 22:42
4

Rather than WNetUseConnection, I would recommend NetUseAdd. WNetUseConnection is a legacy function that's been superceded by WNetUseConnection2 and WNetUseConnection3, but all of those functions create a network device that's visible in Windows Explorer. NetUseAdd is the equivalent of calling net use in a DOS prompt to authenticate on a remote computer.

If you call NetUseAdd then subsequent attempts to access the directory should succeed.

Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
  • 1
    @Adam Robinson: THis is not true. There is no such WNetUseConnection2 nor WNetUseConnection3. I think you are thinkign about WNetAddConnection being superceded by WNetAddConnection2 and WnetAddConnection3. Also the information you gave about it is not true. – Brian R. Bondy Mar 26 '09 at 12:13
  • WNetUseConnection is like WNetAddConnection3, but it also has an optional ability to create a mapped local drive. Which you don't have to use. – Brian R. Bondy Mar 26 '09 at 12:14
  • @BrianR.Bondy They do indeed exist, just not implemented as C#. Source: https://learn.microsoft.com/da-dk/windows/win32/api/lmuse/nf-lmuse-netuseadd?redirectedfrom=MSDN Quote: "You can also use the WNetAddConnection2 and WNetAddConnection3 functions to redirect a local device to a network resource." – Thomas Williams Dec 20 '19 at 20:24
2

Most SFTP servers support SCP as well which can be a lot easier to find libraries for. You could even just call an existing client from your code like pscp included with PuTTY.

If the type of file you're working with is something simple like a text or XML file, you could even go so far as to write your own client/server implementation to manipulate the file using something like .NET Remoting or web services.

Ryan Bolger
  • 1,275
  • 8
  • 20
1

im attach my vb.net code based on brian reference

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

how to use it

Dim login = PinvokeWindowsNetworking.connectToRemote("\\ComputerName", "ComputerName\UserName", "Password")

    If IsNothing(login) Then



        'do your thing on the shared folder



       PinvokeWindowsNetworking.disconnectRemote("\\ComputerName")

    End If
roy.d
  • 1,030
  • 2
  • 16
  • 33
-2

I looked to MS to find the answers. The first solution assumes the user account running the application process has access to the shared folder or drive (Same domain). Make sure your DNS is resolved or try using IP address. Simply do the following:

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

If you want across different domains .NET 2.0 with credentials follow this model:

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();
Kentonbmax
  • 938
  • 1
  • 10
  • 16