简体   繁体   中英

System.Diagnostics.Process.Start a process against a different domain

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:

  1. User arrives at the office and logs in to the corporate domain (lets call it LOCALDOMAIN for simplicity)
  2. They wish to connect to our remote database on a different domain (lets call it REMOTEDOMAIN)
  3. First they launch the VPN tool which establishes the VPN tunnel to REMOTEDOMAIN (this is all tested and works great)
  4. 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");
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM