How to provide user name and password when connecting to a network share

330,899

Solution 1

You can either change the thread identity, or P/Invoke WNetAddConnection2. I prefer the latter, as I sometimes need to maintain multiple credentials for different locations. I wrap it into an IDisposable and call WNetCancelConnection2 to remove the creds afterwards (avoiding the multiple usernames error):

using (new NetworkConnection(@"\\server\read", readCredentials))
using (new NetworkConnection(@"\\server2\write", writeCredentials)) {
   File.Copy(@"\\server\read\file", @"\\server2\write\file");
}

Solution 2

I liked Mark Brackett's answer so much that I did my own quick implementation. Here it is if anyone else needs it in a hurry:

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
}

Solution 3

Today 7 years later I'm facing the same issue and I'd like to share my version of the solution.

It is copy & paste ready :-) Here it is:

Step 1

In your code (whenever you need to do something with permissions)

ImpersonationHelper.Impersonate(domain, userName, userPassword, delegate
                            {
                                //Your code here 
                                //Let's say file copy:
                                if (!File.Exists(to))
                                {
                                    File.Copy(from, to);
                                }
                            });

Step 2

The Helper file which does a magic

using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;    
using Microsoft.Win32.SafeHandles;


namespace BlaBla
{
    public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle()
            : base(true)
        {
        }

        [DllImport("kernel32.dll")]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [SuppressUnmanagedCodeSecurity]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr handle);

        protected override bool ReleaseHandle()
        {
            return CloseHandle(handle);
        }
    }

    public class ImpersonationHelper
    {
        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
        int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);

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

        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        public static void Impersonate(string domainName, string userName, string userPassword, Action actionToExecute)
        {
            SafeTokenHandle safeTokenHandle;
            try
            {

                const int LOGON32_PROVIDER_DEFAULT = 0;
                //This parameter causes LogonUser to create a primary token.
                const int LOGON32_LOGON_INTERACTIVE = 2;

                // Call LogonUser to obtain a handle to an access token.
                bool returnValue = LogonUser(userName, domainName, userPassword,
                    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                    out safeTokenHandle);
                //Facade.Instance.Trace("LogonUser called.");

                if (returnValue == false)
                {
                    int ret = Marshal.GetLastWin32Error();
                    //Facade.Instance.Trace($"LogonUser failed with error code : {ret}");

                    throw new System.ComponentModel.Win32Exception(ret);
                }

                using (safeTokenHandle)
                {
                    //Facade.Instance.Trace($"Value of Windows NT token: {safeTokenHandle}");
                    //Facade.Instance.Trace($"Before impersonation: {WindowsIdentity.GetCurrent().Name}");

                    // Use the token handle returned by LogonUser.
                    using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
                    {
                        using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
                        {
                            //Facade.Instance.Trace($"After impersonation: {WindowsIdentity.GetCurrent().Name}");
                            //Facade.Instance.Trace("Start executing an action");

                            actionToExecute();

                            //Facade.Instance.Trace("Finished executing an action");
                        }
                    }
                    //Facade.Instance.Trace($"After closing the context: {WindowsIdentity.GetCurrent().Name}");
                }

            }
            catch (Exception ex)
            {
                //Facade.Instance.Trace("Oh no! Impersonate method failed.");
                //ex.HandleException();
                //On purpose: we want to notify a caller about the issue /Pavel Kovalev 9/16/2016 2:15:23 PM)/
                throw;
            }
        }
    }
}

Solution 4

I searched lots of methods and i did it my own way. You have to open a connection between two machine via command prompt NET USE command and after finishing your work clear the connection with command prompt NET USE "myconnection" /delete.

You must use Command Prompt process from code behind like this:

var savePath = @"\\servername\foldername\myfilename.jpg";
var filePath = @"C:\\temp\myfileTosave.jpg";

Usage is simple:

SaveACopyfileToServer(filePath, savePath);

Here is functions:

using System.IO
using System.Diagnostics;


public static void SaveACopyfileToServer(string filePath, string savePath)
    {
        var directory = Path.GetDirectoryName(savePath).Trim();
        var username = "loginusername";
        var password = "loginpassword";
        var filenameToSave = Path.GetFileName(savePath);

        if (!directory.EndsWith("\\"))
            filenameToSave = "\\" + filenameToSave;

        var command = "NET USE " + directory + " /delete";
        ExecuteCommand(command, 5000);

        command = "NET USE " + directory + " /user:" + username + " " + password;
        ExecuteCommand(command, 5000);

        command = " copy \"" + filePath + "\"  \"" + directory + filenameToSave + "\"";

        ExecuteCommand(command, 5000);


        command = "NET USE " + directory + " /delete";
        ExecuteCommand(command, 5000);
    }

And also ExecuteCommand function is:

public static int ExecuteCommand(string command, int timeout)
    {
        var processInfo = new ProcessStartInfo("cmd.exe", "/C " + command)
                              {
                                  CreateNoWindow = true, 
                                  UseShellExecute = false, 
                                  WorkingDirectory = "C:\\",
                              };

        var process = Process.Start(processInfo);
        process.WaitForExit(timeout);
        var exitCode = process.ExitCode;
        process.Close();
        return exitCode;
    } 

This functions worked very fast and stable for me.

Solution 5

The Luke Quinane solution looks good, but did work only partially in my ASP.NET MVC application. Having two shares on the same server with different credentials I could use the impersonation only for the first one.

The problem with WNetAddConnection2 is also that it behaves differently on different windows versions. That is why I looked for alternatives and found the LogonUser function. Here is my code which also works in ASP.NET:

public sealed class WrappedImpersonationContext
{
    public enum LogonType : int
    {
        Interactive = 2,
        Network = 3,
        Batch = 4,
        Service = 5,
        Unlock = 7,
        NetworkClearText = 8,
        NewCredentials = 9
    }

    public enum LogonProvider : int
    {
        Default = 0,  // LOGON32_PROVIDER_DEFAULT
        WinNT35 = 1,
        WinNT40 = 2,  // Use the NTLM logon provider.
        WinNT50 = 3   // Use the negotiate logon provider.
    }

    [DllImport("advapi32.dll", EntryPoint = "LogonUserW", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool LogonUser(String lpszUsername, String lpszDomain,
        String lpszPassword, LogonType dwLogonType, LogonProvider dwLogonProvider, ref IntPtr phToken);

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

    private string _domain, _password, _username;
    private IntPtr _token;
    private WindowsImpersonationContext _context;

    private bool IsInContext
    {
        get { return _context != null; }
    }

    public WrappedImpersonationContext(string domain, string username, string password)
    {
        _domain = String.IsNullOrEmpty(domain) ? "." : domain;
        _username = username;
        _password = password;
    }

    // Changes the Windows identity of this thread. Make sure to always call Leave() at the end.
    [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
    public void Enter()
    {
        if (IsInContext)
            return;

        _token = IntPtr.Zero;
        bool logonSuccessfull = LogonUser(_username, _domain, _password, LogonType.NewCredentials, LogonProvider.WinNT50, ref _token);
        if (!logonSuccessfull)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        WindowsIdentity identity = new WindowsIdentity(_token);
        _context = identity.Impersonate();

        Debug.WriteLine(WindowsIdentity.GetCurrent().Name);
    }

    [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
    public void Leave()
    {
        if (!IsInContext)
            return;

        _context.Undo();

        if (_token != IntPtr.Zero)
        {
            CloseHandle(_token);
        }
        _context = null;
    }
}

Usage:

var impersonationContext = new WrappedImpersonationContext(Domain, Username, Password);
impersonationContext.Enter();

//do your stuff here

impersonationContext.Leave();
Share:
330,899

Related videos on Youtube

gyrolf
Author by

gyrolf

Updated on December 18, 2020

Comments

  • gyrolf
    gyrolf over 3 years

    When connecting to a network share for which the current user (in my case, a network enabled service user) has no rights, name and password have to be provided.

    I know how to do this with Win32 functions (the WNet* family from mpr.dll), but would like to do it with .Net (2.0) functionality.

    What options are available?

    Maybe some more information helps:

    • The use case is a windows service, not an Asp.Net application.
    • The service is running under an account which has no rights on the share.
    • The user account needed for the share is not known on the client side.
    • Client and server are not members of the same domain.
    • Moose
      Moose over 15 years
      While I'm not giving you a useful answer, I can supply an anti-answer.. Impersonation and spawning a process as Marc posited will not work when the server and the client are not in the same domain, unless there is a trust between the two domains. If there is a trust then I think it will work. I would have just replied as a comment to Marc's but I don't have enough rep to comment. :-/
    • vapcguy
      vapcguy over 7 years
  • ubi
    ubi over 15 years
    i don't think the process thing is such a bad idea. google put out some whitepapers about the benefits of multiprocessing in chrome.
  • gyrolf
    gyrolf over 15 years
    Is it possible to change the thread principal to an user with no account on the local machine?
  • Marc Gravell
    Marc Gravell over 15 years
    To be honest, I simply don't know... You'd have to try LogonUser with a different domain to find out.
  • stephbu
    stephbu over 15 years
    The service isn't member of the target domain - impersonation cannot work since you wouldn't be able to create the security token locally and impersonate with it. PInvoke is the only way.
  • torvin
    torvin about 13 years
    It really should be throw new Win32Exception(result);, since WNetAddConnection2 returns win32 error codes (ERROR_XXX)
  • AngryHacker
    AngryHacker almost 12 years
    Note that if you are connecting to a domain resource, it should be string.Format(@"{0}\{1}", credentials.Domain, credentials.UserName) instead of just credentials.UserName.
  • Otiel
    Otiel over 11 years
    Can you explain where does ResourceScope.Recent and ResourceScope.Context come from? Since dwScope seems to have only 3 values possible (as written here): RESOURCE_CONNECTED, RESOURCE_GLOBALNET, RESOURCE_REMEMBERED. Also, where does ResourceType.Reserved comes from?
  • Luke Quinane
    Luke Quinane over 11 years
    I got those structures from pinvoke.net here and here. If you search around for RESOURCE_RECENT and RESOURCETYPE_RESERVED there are a few reactos hits so perhaps they are undocumented values?
  • Matt
    Matt about 11 years
    Great solution, thanks. I wanted to add that in our case, we were connecting from inside our network, to a Windows2008R2 server on the DMZ with an account that was local to the DMZ box. In this case, paths with \\server\d$\somepath in the path would not work, we had to explicity share the directory such that we could visit it using \\server\somepath instead. Otherwise we would get remote connection errors.
  • Tay
    Tay over 10 years
    This is a brilliant little piece of code. Needed to logon to a UNIX system to get a directory listing for printing to an MVC5 web application and this did the trick. +1!!!
  • Rainer Schaack
    Rainer Schaack over 9 years
    Nice class. I just gave a link to this anwer for accessing a remote registry with credentials: stackoverflow.com/a/28749093/4547223
  • kellyb
    kellyb over 8 years
    this approach worked well for me, but noticed in my testing that when using a bad password with a domain user account, that user is immediately throw into locked status. our domain policy calls for a 3 failed login attempts before that happens, but via this approach one bad attempt and you're locked. So, use with caution...
  • MelnikovI
    MelnikovI over 8 years
    So solution works perfect. But in fact connection is not clodes after NetworkConnection dispose. After object dispose i didnt see connetion via net use as expected, but i can still acess folder for a while. So question is : how to prevent acess to shared folder after object disposion. Anyone knows reason why this happens?
  • Matt Nelson
    Matt Nelson over 8 years
    The following using statements are required in order for the code above to compile: using System.Net; using System.Runtime.InteropServices; using System.ComponentModel;
  • arti
    arti over 8 years
    sorry to refresh that old thread, but looks like it doesn't close connection after block is finished. I have a program to upload few pictures, first one goes fine, second one giving fail. Connection is released when program is closed. Any advise?
  • Breeze
    Breeze over 8 years
    @MarkBrackett I know this is an old answer, but maybe you still know... will the access be granted to the program only or also to the logged in user via explorer?
  • Mark Brackett
    Mark Brackett over 8 years
    @Breeze - I haven't tested it, but I'd expect it authenticate for the logon session; so if your program is running as the logged on user, they'd have access as well (at least for the duration of the operation).
  • Breeze
    Breeze over 8 years
    @MarkBrackett I meanwhile tested it and your expectation was correct.
  • lsmeby
    lsmeby almost 8 years
    We had the same problem as you, @arti . By just setting the username and password on the NetworkCredential object the application was able to connect once to the network drive. After that we got an ERROR_LOGON_FAILURE on each attempt until the application was restarted. We then tried to supply the domain on the NetworkCredential object as well, and suddenly it worked! I have no idea why this fixed the issue, especially the fact that it worked to connect once without the domain.
  • Anders Lindén
    Anders Lindén about 7 years
    The definitions of readCredentials and writeCredentials could be included in the answer.
  • Can Sahin
    Can Sahin almost 7 years
    According to the documentation of WNetAddConnection2, the Scope and DisplayType values are ignored, so there's no need to set them.
  • nlareu
    nlareu almost 7 years
    I used this solution many times and it worked perfect. But now I am trying to connect a remote share folder with a user that has no password set and it didn't work. Is this possible? What I need to change to make it work?
  • Charles Chen
    Charles Chen over 6 years
    @MohammadRashid According to the documentation on LogonUser, it only works for users on the local computer: "The LogonUser function attempts to log a user on to the local computer. The local computer is the computer from which LogonUser was called. You cannot use LogonUser to log on to a remote computer. " You'll receive an error "Win32Exception: The user name or password is incorrect." So I suppose the machines need to be on the same domain at least.
  • Brian MacKay
    Brian MacKay over 6 years
    @CharlesChen Just proved that this works fine across domains, FYI. The server I'm running this on is in a DMZ, and is definitely connecting to a file server on a different domain, through a firewall. Killer snippet Pavel, you are the man, and this should probably be the accepted answer today.
  • Mustafa Sadedil
    Mustafa Sadedil about 6 years
    If you're getting Error 53, make sure the path isn't ending with a "\"
  • Dutchman
    Dutchman almost 6 years
    You may want to add networkName-parameter validation, or autofix this using something like this: ` _networkName = networkName.TrimEnd(System.IO.Path.DirectorySeparatorChar); `
  • Jim Wolff
    Jim Wolff over 5 years
    @LukeQuinane i removed message part from Win32Exception that way the exception.Message will become the actual message from the error code.
  • Alicia
    Alicia over 5 years
    Two comments: my mpr.dll (Windows 7) does not expose methods WNetAddConnection2 and WNetCancelConnection2, instead I had to use WNetAddConnection2A and WNetCancelConnection2A... Second, the path cannot end with "\"... If I remove it it works, but if I leave everything the same with the \ at the end, it issues an error saying that it cannot faind the path.
  • surega
    surega over 5 years
    In case the share mapping fails, what would the return codes be?
  • Jim
    Jim over 5 years
    Some Corporate Security prevents the use of impersonate because they are unable to track the application using it and must be in same or trusted domain. I think impersonate support is spotted. A domain service account with pinvoke appears to be the way to go.
  • STLDev
    STLDev over 4 years
    This is A GREAT SOLUTION! Thank you, Pavel Kovalev.
  • Tom Lint
    Tom Lint over 4 years
    @Alicia "my mpr.dll (Windows 7) does not expose methods WNetAddConnection2 and WNetCancelConnection2, instead I had to use WNetAddConnection2A and WNetCancelConnection2A" That is correct. Window DLLs usually expose both an ANSI (A) or UNICODE (W) version of functions. Depending on the values passed to the DllImport attribute in the CharSet argument, P/Invoke will automagically call the right version, in which case the A or W postfix can be omitted.
  • Alicia
    Alicia over 4 years
    @TomLint yes, actually after posting this comment I opened a question (stackoverflow.com/questions/52643817/…) to get more details on that point, and I got them :)
  • Julius Limson
    Julius Limson over 4 years
    does this work on ldap? it says that i don't have a logon server available. im using ldap auth
  • vandsh
    vandsh over 4 years
    Reading in the remoteHost as a Uri could really help this solution. I had to keep fighting with the actual path I was passing in and receiving fairly vague answers. That was until I found stackoverflow.com/questions/1363679/… and helped do some proper path verification.
  • Ankush Jain
    Ankush Jain about 4 years
    @lsmeby - Thanks a ton man...you gave me the solution that I was looking from last 7 days.
  • Ankush Jain
    Ankush Jain about 4 years
    @LukeQuinane - You should create a NuGet package for this. That would be a great help.
  • Himanshu Patel
    Himanshu Patel almost 4 years
    Brilliant code. I had a shared folder on an Azure server and had to copy files locally. This worked beautifully.
  • Wolfgang Roth
    Wolfgang Roth almost 4 years
    some more years later, it seems, that this is not working for me. I am using windows 10 on both ends of the connection. the ip-adress of the target pc is 192.168.10.255 and the user is "user", a local user on this pc. I tried domain with and without \\ also user with and without domain, but i cannot login. Login via windows works perfect.
  • Pavel Kovalev
    Pavel Kovalev almost 4 years
    @WolfgangRoth The LogonUser function attempts to log a user on to the local computer. The local computer is the computer from which LogonUser was called. You cannot use LogonUser to log on to a remote computer. So you can only use it on your local device to access some (shared) folders.
  • Vasanth
    Vasanth over 3 years
    Getting error, The type or namespace name 'NetworkCredential' could not be found (are you missing a using directive or an assembly reference?) can someone please help, from where should I import it ?
  • SzilardD
    SzilardD over 3 years
    this solution worked for me, but after a while out of nowhere, WNetAddConnection2 started returning error code 1219. three changes solved the issue for me: 1. ignore error code 1219; 2. provide the domain to the NetworkCredential object; 3. create a and then close a new connection (NetworkConnection) every time when accessing the remote share (previously multiple file operations were being done part of a single connection doing multiple actions, which was closed/disposed at the end)