System.Diagnostics.Process.Start a process against a different domain - c#

We have a scenario where we need our users to be able to launch SQLServer and authenticate into it using a different domain than they are currently logged into. So to clarify the way this is setup:
User arrives at the office and logs in to the corporate domain (lets call it LOCALDOMAIN for simplicity)
They wish to connect to our remote database on a different domain (lets call it REMOTEDOMAIN)
First they launch the VPN tool which establishes the VPN tunnel to REMOTEDOMAIN (this is all tested and works great)
But if they launch SSMS by default it will only allow Windows Auth via LOCALDOMAIN, the option to select REMOTEDOMAIN is not even available
What we discovered is that running this from the command line will work:
RUNAS /user:REMOTEDOMAIN\AUserName /netonly "C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe
it will prompt with the message "Enter the password for REMOTEDOMAIN\AUserName:" and if you supply the correct password, SSMS will be launched and can connect to the remote dbs. However, when I try to do the same thing in C# with a nicer interface around it, I get "Logon failure: unknown user name or bad password", here is my code:
System.Security.SecureString password = new System.Security.SecureString();
foreach(char c in txtPassword.Text.ToCharArray()){
password.AppendChar(c);
}
System.Diagnostics.ProcessStartInfo procInfo = new System.Diagnostics.ProcessStartInfo();
procInfo.Arguments = "/netonly";
procInfo.FileName = #"C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe"; ;
procInfo.Domain = "REMOTEDOMAIN";
procInfo.Verb = "runas";
procInfo.UserName = txtUsername.Text;
procInfo.Password = password;
procInfo.UseShellExecute = false;
System.Diagnostics.Process.Start(procInfo);
I tried the username with and without the domain pre-pended but neither works. Anyone ever tried to do something similar? thanks

You should remove the following lines:
// Not passing /netonly to SMSS, it was passed to RunAs originally.
procInfo.Arguments = "/netonly";
// Again, SMSS is not getting the verb, it's being run
procInfo.Verb = "runas";
Basically, you're passing the /netonly parameter to SMSS, whereas on the command line, you're running runas not SMSS. Same with the verb, you're not running runas.
The call to Start should succeed at that point, as you'll be pointing to the correct executable with the correct credentials.

I've done something that may be related. I login to one domain and try to get a directory listing of a shared folder on a different domain. To do this, I use LogonUser and Impersonate. The code looks like the following (sorry, I don't have an SQL server to try your exact scenario)...
public class Login : IDisposable
{
public Login(string userName, string domainName)
{
_userName = userName;
_domainName = domainName;
}
string _userName = null;
string _domainName = null;
IntPtr tokenHandle = new IntPtr(0);
IntPtr dupeTokenHandle = new IntPtr(0);
WindowsImpersonationContext impersonatedUser = null;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "LogonUser")]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "LogonUser")]
public static extern bool LogonUserPrompt(String lpszUsername, String lpszDomain, IntPtr lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
public void AccessShare(string password)
{
tokenHandle = IntPtr.Zero;
bool returnValue = LogonUser(_userName, _domainName, password,
LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
ref tokenHandle);
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
// Use the token handle returned by LogonUser.
WindowsIdentity newId = new WindowsIdentity(tokenHandle);
impersonatedUser = newId.Impersonate();
}
#region IDisposable Members
public void Dispose()
{
impersonatedUser.Undo();
// Free the tokens.
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
}
#endregion
}
I've used this with Directory.GetDirectories(UNCPath) where the path leads to a machine on another domain and it works there. I have not yet tried it for implementing a "runas".
I call it like so...
using(var login = new Login("myname","mydomain))
{
login.AccessShare("mypassword");
// do stuff
}
Maybe you can adapt it to your problem. LMK

I tried all the various user impersonation code samples I could find. None of them worked.
Finally, I came up with the following code. It executes cmd.exe with the /C argument, which Carries out the command specified by string and then terminates . The command that I execute is runas /netonly ...
Caveats
Unfortunately, the password has to be typed manually. My next step is to investigate sending key stokes to the process. I tried redirecting standard input and writing to it, but it didn't work. I read somewhere on SO that most password prompts only accept input directly from the keyboard.
Also, when SSMS opens, the Connect to Server dialog will show your current domain\username, but it will authenticate using the one you gave to runas.
Finally, if your AD account is locked, you won't get an error until you try to connect to SQL Server. I neglected to copy down the error message that I received, but it did not mention the account was locked.
Code
public static void RunAsNetonly(string username, string domain, string exePath)
{
var psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.Arguments = $"/C runas /netonly /user:{domain}\\{username} \"{exePath}\"";
psi.UseShellExecute = false;
var process = Process.Start(psi);
// not sure if this is required
process.WaitForExit();
}
// usage example
public static void RunSSMS()
{
RunAsNetonly("walter", "domain123", #"C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\ssms.exe");
}

Related

.Net Core Impersonation not working with Process.Start

I can't seem to start a process as another user when using impersonation under .Net Core.
I'm running this script in Linqpad running as User1 and trying to launch a program as User2.
At first, the impersonation seems to work (the Console.Writeline()s on the current user change correctly from User1 to User2 in the RunImpersonated() Method). However, the process always runs as User1.
This is one of many tests I'm doing to validate that RunImpersonated() works (this originally stems from issues in an ASP.Net Core App trying to impersonate the current user). This is the simplest reproducible example I could find.
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken);
void Main()
{
string domainName = "myDomain";
string userName = "User2";
string passWord = "User2Password";
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.
SafeAccessTokenHandle safeAccessTokenHandle;
bool returnValue = LogonUser(userName, domainName, passWord,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeAccessTokenHandle);
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
Console.WriteLine("LogonUser failed with error code : {0}", ret);
throw new System.ComponentModel.Win32Exception(ret);
}
Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
// Check the identity.
Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);
// Note: if you want to run as unimpersonated, pass
// 'SafeAccessTokenHandle.InvalidHandle' instead of variable 'safeAccessTokenHandle'
WindowsIdentity.RunImpersonated(
safeAccessTokenHandle,
// User action
() =>
{
// Check the identity.
Console.WriteLine("During impersonation: " + WindowsIdentity.GetCurrent().Name);
Directory.GetFiles(#"C:\TMP\").Dump();
var pi = new ProcessStartInfo
{
WorkingDirectory = #"C:\TMP\",
FileName = #"C:\TMP\TestUser.exe"
};
var proc = Process.Start(pi);
proc.WaitForExit();
}
);
// Check the identity again.
Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name);
}
If you don't specify username and password, Process.Start will uses the token for the calling process, not the impersonation token.
Looking into source code of Process.Start:
If you pass user name and password it uses CreateProcess
If you don't specify username and password, it calls CreateProcessWithLogon
If the calling process is impersonating another user, the new process
uses the token for the calling process, not the impersonation token.
To run the new process in the security context of the user represented
by the impersonation token, use the CreateProcessAsUser or
CreateProcessWithLogonW function.
Without passing username and password the process always runs on security context of the original process owner. If you want to run the process under the context of another user:
If you have username and password: use CreateProcessWithLogon
If you have a to the primary token that represents a user use CreateProcessAsUser
There are multiple impersonation levels. Documentation states that you cannot use thread impersonation token to start the process - primary token will always be used:
The system uses the primary token of the process rather than the
impersonation token of the calling thread in the following situations:
If an impersonating thread calls the CreateProcess function, the new
process always inherits the primary token of the process.
Given you don't have user's password and you want to use impersonation token instead to start a process, unfortunately, the answer is - you can't do that.
You can impersonate caller for other operations though (RPC, COM, FS...).
A while back I used the following code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;
using System.Text;
using System.Windows;
using Microsoft.Win32.SafeHandles;
namespace ZZZ
{
partial class User : IDisposable
{
private string m_domain;
private string m_user;
private string m_pass;
private WindowsIdentity user;
private SafeTokenHandle safeTokenHandle;
private WindowsImpersonationContext impContext;
[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);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool RevertToSelf();
//For the current user
public User()
{
user = WindowsIdentity.GetCurrent();
}
// For custom user
public User(string domain, string user, string password, bool doImepsonate = false)
{
m_domain = domain;
m_user = user;
m_pass = password;
if (doImepsonate) this.Impersonate();
}
// If it's intended to incorporate this code into a DLL, then demand FullTrust.
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public void Impersonate()
{
if (impContext != null) throw new ImpersonationException();
try
{
// Get the user token for the specified user, domain, and password using the unmanaged LogonUser method.
// The local machine name can be used for the domain name to impersonate a user on this machine.
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(
m_user,
m_domain,
m_pass,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
if (returnValue == false)
{
int ret = Marshal.GetLastWin32Error();
throw new Win32Exception(ret);
}
using (safeTokenHandle)
{
// Use the token handle returned by LogonUser.
user = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
impContext = user.Impersonate();
}
}
catch (Exception ex)
{
MessageBox.Show("Exception occurred:\n" + ex.Message);
}
}
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
private void Quit()
{
if (impContext == null) return;
impContext.Undo();
safeTokenHandle.Dispose();
}
#endregion
internal IEnumerable<string> Groups
{
get
{
return user.Groups.Select(p =>
{
IdentityReference ir = null;
try { ir = p.Translate(typeof(NTAccount)); }
catch { }
return ir == null ? null : ir.Value;
});
}
}
}
// Win32 API part
internal 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);
}
}
internal sealed class ImpersonationException : Exception
{
public ImpersonationException() : base("The user is already impersonated.") { }
}
}
This works great for me. Check the user who is running the process. Sometime the user is not administrator or cannot impersonate.
Processinfo creates new process. Try process.start, Or you can convert exe to util dll and run inside code like utli.testuser code. Use dll call method from main program and not exe.
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.
SafeAccessTokenHandle safeAccessTokenHandle;
bool returnValue = LogonUser(userName, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeAccessTokenHandle);
WindowsIdentity.RunImpersonated(safeAccessTokenHandle, () => {
Var impersonatedUser = WindowsIdentity.GetCurrent().Name;
//--- Call your Method here…….
});

Selenium C# - exception when run selenium driver after impersonate

I am using windows functionality, impersonate, to change the active user before starting Chrome driver. Now, starting the driver without the impersonate code works fine. Also the impersonate code works fine; I see the user is changed. But when this change happens and after that I run IWebDriver driver=new ChromeDriver then the exception is triggered on that exact code and the test stops. Any ideas why this happens?
Here is main part of the code used (the code is just little modified code from another post here at stackoverflow)
namespace localSeleniumTest.Impersonation
{
class Program
{
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(stringpszUsername, string pszDomain, string pszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
// closes open handes returned by LogonUser
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
public void Impersonation()
{
WindowsImpersonationContext impersonationContext = null;
IntPtr userHandle = IntPtr.Zero;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
string domain = Config.domain;
string user = Config.username;
string password = Config.password;
try
{
String currentName = WindowsIdentity.GetCurrent().Name;
// if domain name was blank, assume local machine
if (domain == "")
domain = System.Environment.MachineName;
// Call LogonUser to get a token for the user
bool loggedOn = LogonUser(
user,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref userHandle
);
if (!loggedOn)
{
return;
}
// Begin impersonating the user
impersonationContext = WindowsIdentity.Impersonate(userHandle);
String afterImpersonationName = WindowsIdentity.GetCurrent().Name;
/*this few lines below does not work after impersonation but
work perfectly without the code above.*/
IWebDriver driver = new ChromeDriver();
driver.Navigate().GoToUrl("www.google.com");
System.Threading.Thread.Sleep(6000);
driver.Quit();
Found the issue. D user did not have permission to chrome driver i Bin folder

access a directory in a domain by username and password

I have developed an application in c# using visual studio 2015, that copies some files from one directory (source) to another (destination) in general.
My problem is that the source path is another computer in a domain. I wish to be able to access the directory and get my files, user the domain, username and password the source computer.
I have seen some solution, but I can't get how they access the other computer.
I used to get my files by using the Directory. GetDirectories (path), and I'm far too deep using it now and can't change it to smooth as.
thanks for helping me with my problem I'm truly blocked for days now.
string[] folder1;
string[] folder2;
folder1 = Directory.GetDirectories(path);
foreach (string fld1 in folder1)
{
folder2 = Directory.GetDirectories(fld);
foreach(string fld2 in folder2)
{
for(int i = 0; i < MyList.Count(); i++)
{
if(fld2.Contains("nok") || fld2.Contains("Nok"))
LNok = Directory.GetFiles(fld2, picList[i]);
else
Lok = Directory.GetFiles(fld2, picList[i]);
}
}
}
Since System.IO.File and System.IO.Directory methods don’t support passing credentials, the preferred solution is to impersonate an authorized user account. This requires to import two methods from the advapi32.dll and kernel32.dll using pInvoke:
//Impersonation functionality
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
//Disconnection after file operations
[DllImport("kernel32.dll")]
private static extern Boolean CloseHandle(IntPtr hObject);
The following code makes use of those methods to create a WindowsImpersonationContext
const int LOGON_TYPE_NEW_CREDENTIALS = 9;
const int LOGON32_PROVIDER_WINNT50 = 3;
//User token that represents the authorized user account
IntPtr token = IntPtr.Zero;
bool result = LogonUser("username", "domainname", "password", LOGON_TYPE_NEW_CREDENTIALS , LOGON32_PROVIDER_WINNT50, ref token);
if (result == true)
{
//Use token to setup a WindowsImpersonationContext
using (WindowsImpersonationContext ctx = new WindowsIdentity(token).Impersonate())
{
//Your file operations
string[] files = Directory.GetFiles(#"\\remotemachine\share\folder");
//Release the context, and close user token
ctx.Undo();
CloseHandle(token);
}
}
Here you may find the MSDN documentation of the LogonUser function

Impersonate local user on another machine

I need to log on my controller to another machine and copy a file on it; I have to use a local user on the remote machine.
Currently I'm using this code:
private Impersonate(bool active, string domain, string username, string password, LogonType logonType)
{
if (active)
{
IntPtr handle;
var ok = NativeMethods.LogonUser(username, domain, password, (int)logonType, 0, out handle);
if (!ok)
{
var errorCode = Marshal.GetLastWin32Error();
throw new ApplicationException(string.Format("Could not impersonate the elevated user. LogonUser returned error code {0}.", errorCode));
}
_handle = new SafeTokenHandle(handle);
_context = WindowsIdentity.Impersonate(_handle.DangerousGetHandle());
}
}
passing these args:
using (Impersonate.LogonUser(true,
".",
"todev1.domain.com\admin",
"Test123_",
LogonType.Interactive))
{
}
and this win API:
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
I checked this Q/A Using advapi32.dll:LogonUserA() to impersonate a remote machine's local user but the solution provided is not working.
I tried to pass multiple values to the method as domain, user and so on, but I can't find the rigth solution. I tried using NewCredentials but it returns always ok even if not logged.
I finally solved this issue, without the need to add the user to each machine that will impersonate to the remote machine.
It's correct to use NewCredential, but using the WINNT50 LogonProvider.
So my impersonate method now is like:
private Impersonate(bool active, string domain, string username, string password, LogonType logonType, LogonProvider logonProvider)
{
if (active)
{
IntPtr handle;
var ok = NativeMethods.LogonUser(username, domain, password, (int)logonType, (int)logonProvider, out handle);
if (!ok)
{
var errorCode = Marshal.GetLastWin32Error();
throw new ApplicationException(string.Format("Could not impersonate the elevated user. LogonUser returned error code {0}.", errorCode));
}
_handle = new SafeTokenHandle(handle);
_context = WindowsIdentity.Impersonate(_handle.DangerousGetHandle());
}
}
Then I call the Impersonate method using the code:
using (Impersonate.LogonUser(true,
"todev1.domain.com",
"admin",
"Test123_",
LogonType.NewCredentials,
LogonProvider.WinNT50))
{
}
You can try to make a local user on your local machine with the same username and password as the local user on the remote server.

Waiting for prompt from Process.StandardOutput [duplicate]

We have a scenario where we need our users to be able to launch SQLServer and authenticate into it using a different domain than they are currently logged into. So to clarify the way this is setup:
User arrives at the office and logs in to the corporate domain (lets call it LOCALDOMAIN for simplicity)
They wish to connect to our remote database on a different domain (lets call it REMOTEDOMAIN)
First they launch the VPN tool which establishes the VPN tunnel to REMOTEDOMAIN (this is all tested and works great)
But if they launch SSMS by default it will only allow Windows Auth via LOCALDOMAIN, the option to select REMOTEDOMAIN is not even available
What we discovered is that running this from the command line will work:
RUNAS /user:REMOTEDOMAIN\AUserName /netonly "C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe
it will prompt with the message "Enter the password for REMOTEDOMAIN\AUserName:" and if you supply the correct password, SSMS will be launched and can connect to the remote dbs. However, when I try to do the same thing in C# with a nicer interface around it, I get "Logon failure: unknown user name or bad password", here is my code:
System.Security.SecureString password = new System.Security.SecureString();
foreach(char c in txtPassword.Text.ToCharArray()){
password.AppendChar(c);
}
System.Diagnostics.ProcessStartInfo procInfo = new System.Diagnostics.ProcessStartInfo();
procInfo.Arguments = "/netonly";
procInfo.FileName = #"C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe"; ;
procInfo.Domain = "REMOTEDOMAIN";
procInfo.Verb = "runas";
procInfo.UserName = txtUsername.Text;
procInfo.Password = password;
procInfo.UseShellExecute = false;
System.Diagnostics.Process.Start(procInfo);
I tried the username with and without the domain pre-pended but neither works. Anyone ever tried to do something similar? thanks
You should remove the following lines:
// Not passing /netonly to SMSS, it was passed to RunAs originally.
procInfo.Arguments = "/netonly";
// Again, SMSS is not getting the verb, it's being run
procInfo.Verb = "runas";
Basically, you're passing the /netonly parameter to SMSS, whereas on the command line, you're running runas not SMSS. Same with the verb, you're not running runas.
The call to Start should succeed at that point, as you'll be pointing to the correct executable with the correct credentials.
I've done something that may be related. I login to one domain and try to get a directory listing of a shared folder on a different domain. To do this, I use LogonUser and Impersonate. The code looks like the following (sorry, I don't have an SQL server to try your exact scenario)...
public class Login : IDisposable
{
public Login(string userName, string domainName)
{
_userName = userName;
_domainName = domainName;
}
string _userName = null;
string _domainName = null;
IntPtr tokenHandle = new IntPtr(0);
IntPtr dupeTokenHandle = new IntPtr(0);
WindowsImpersonationContext impersonatedUser = null;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "LogonUser")]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "LogonUser")]
public static extern bool LogonUserPrompt(String lpszUsername, String lpszDomain, IntPtr lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
public void AccessShare(string password)
{
tokenHandle = IntPtr.Zero;
bool returnValue = LogonUser(_userName, _domainName, password,
LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
ref tokenHandle);
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
// Use the token handle returned by LogonUser.
WindowsIdentity newId = new WindowsIdentity(tokenHandle);
impersonatedUser = newId.Impersonate();
}
#region IDisposable Members
public void Dispose()
{
impersonatedUser.Undo();
// Free the tokens.
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
}
#endregion
}
I've used this with Directory.GetDirectories(UNCPath) where the path leads to a machine on another domain and it works there. I have not yet tried it for implementing a "runas".
I call it like so...
using(var login = new Login("myname","mydomain))
{
login.AccessShare("mypassword");
// do stuff
}
Maybe you can adapt it to your problem. LMK
I tried all the various user impersonation code samples I could find. None of them worked.
Finally, I came up with the following code. It executes cmd.exe with the /C argument, which Carries out the command specified by string and then terminates . The command that I execute is runas /netonly ...
Caveats
Unfortunately, the password has to be typed manually. My next step is to investigate sending key stokes to the process. I tried redirecting standard input and writing to it, but it didn't work. I read somewhere on SO that most password prompts only accept input directly from the keyboard.
Also, when SSMS opens, the Connect to Server dialog will show your current domain\username, but it will authenticate using the one you gave to runas.
Finally, if your AD account is locked, you won't get an error until you try to connect to SQL Server. I neglected to copy down the error message that I received, but it did not mention the account was locked.
Code
public static void RunAsNetonly(string username, string domain, string exePath)
{
var psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.Arguments = $"/C runas /netonly /user:{domain}\\{username} \"{exePath}\"";
psi.UseShellExecute = false;
var process = Process.Start(psi);
// not sure if this is required
process.WaitForExit();
}
// usage example
public static void RunSSMS()
{
RunAsNetonly("walter", "domain123", #"C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\ssms.exe");
}

Categories

Resources