How to provide user name and password when connecting to a network share
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();
Related videos on Youtube
gyrolf
Updated on December 18, 2020Comments
-
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 frommpr.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 over 15 yearsWhile 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 over 7 yearsRelated - stackoverflow.com/questions/17786037/…
-
ubi over 15 yearsi don't think the process thing is such a bad idea. google put out some whitepapers about the benefits of multiprocessing in chrome.
-
gyrolf over 15 yearsIs it possible to change the thread principal to an user with no account on the local machine?
-
Marc Gravell over 15 yearsTo be honest, I simply don't know... You'd have to try LogonUser with a different domain to find out.
-
stephbu over 15 yearsThe 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 about 13 yearsIt really should be
throw new Win32Exception(result);
, since WNetAddConnection2 returns win32 error codes (ERROR_XXX
) -
AngryHacker almost 12 yearsNote that if you are connecting to a domain resource, it should be
string.Format(@"{0}\{1}", credentials.Domain, credentials.UserName)
instead of justcredentials.UserName
. -
Otiel over 11 yearsCan you explain where does
ResourceScope.Recent
andResourceScope.Context
come from? Since dwScope seems to have only 3 values possible (as written here):RESOURCE_CONNECTED
,RESOURCE_GLOBALNET
,RESOURCE_REMEMBERED
. Also, where doesResourceType.Reserved
comes from? -
Luke Quinane over 11 years
-
Matt about 11 yearsGreat 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 over 10 yearsThis 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 over 9 yearsNice class. I just gave a link to this anwer for accessing a remote registry with credentials: stackoverflow.com/a/28749093/4547223
-
kellyb over 8 yearsthis 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 over 8 yearsSo solution works perfect. But in fact connection is not clodes after
NetworkConnection
dispose. After object dispose i didnt see connetion vianet 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 over 8 yearsThe following using statements are required in order for the code above to compile: using System.Net; using System.Runtime.InteropServices; using System.ComponentModel;
-
arti over 8 yearssorry 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 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 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 over 8 years@MarkBrackett I meanwhile tested it and your expectation was correct.
-
lsmeby almost 8 yearsWe 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 theNetworkCredential
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 about 7 yearsThe definitions of readCredentials and writeCredentials could be included in the answer.
-
Can Sahin almost 7 yearsAccording to the documentation of WNetAddConnection2, the
Scope
andDisplayType
values are ignored, so there's no need to set them. -
nlareu almost 7 yearsI 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 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 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 about 6 yearsIf you're getting Error 53, make sure the path isn't ending with a "\"
-
Dutchman almost 6 yearsYou may want to add networkName-parameter validation, or autofix this using something like this: ` _networkName = networkName.TrimEnd(System.IO.Path.DirectorySeparatorChar); `
-
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 over 5 yearsTwo 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 over 5 yearsIn case the share mapping fails, what would the return codes be?
-
Jim over 5 yearsSome 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 over 4 yearsThis is A GREAT SOLUTION! Thank you, Pavel Kovalev.
-
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 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 over 4 yearsdoes this work on ldap? it says that i don't have a logon server available. im using ldap auth
-
vandsh over 4 yearsReading in the
remoteHost
as aUri
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 about 4 years@lsmeby - Thanks a ton man...you gave me the solution that I was looking from last 7 days.
-
Ankush Jain about 4 years@LukeQuinane - You should create a NuGet package for this. That would be a great help.
-
Himanshu Patel almost 4 yearsBrilliant code. I had a shared folder on an Azure server and had to copy files locally. This worked beautifully.
-
Wolfgang Roth almost 4 yearssome 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 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 over 3 yearsGetting 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 over 3 yearsthis 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 theNetworkCredential
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)