System.Diagnostics.Process.Start针对不同域的进程

时间:2012-10-23 17:31:09

标签: c# .net process

我们有一个场景,我们需要我们的用户能够启动SQLServer并使用与当前登录时不同的域进行身份验证。所以要澄清这个设置的方式:

  1. 用户到达办公室并登录公司域(为简单起见,我们称之为LOCALDOMAIN)
  2. 他们希望连接到不同域上的远程数据库(我们称之为REMOTEDOMAIN)
  3. 首先他们启动VPN工具,建立到REMOTEDOMAIN的VPN隧道(这都经过测试,效果很好)
  4. 但是如果他们默认启动SSMS,它只允许通过LOCALDOMAIN进行Windows Auth,选择REMOTEDOMAIN的选项甚至不可用
  5. 我们发现从命令行运行它将起作用:

    RUNAS /user:REMOTEDOMAIN\AUserName /netonly "C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe
    

    它将显示“输入REMOTEDOMAIN \ AUserName:的密码”消息,如果您提供了正确的密码,SSMS将启动并可以连接到远程dbs。但是,当我尝试在C#中使用更好的界面做同样的事情时,我得到“登录失败:未知用户名或密码错误”,这是我的代码:

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

    我尝试使用带有和没有域预先填写的用户名,但都不起作用。有人试图做类似的事吗?感谢

3 个答案:

答案 0 :(得分:0)

您应该删除以下行:

// 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";

基本上,您将/netonly参数传递给SMSS,而在命令行中,您正在运行runas 而不是SMSS 。与动词相同,您没有运行runas

此时对Start的调用应该会成功,因为您将使用正确的凭据指向正确的可执行文件。

答案 1 :(得分:0)

我做过一些可能相关的事情。我登录到一个域并尝试获取不同域上的共享文件夹的目录列表。为此,我使用LogonUser和Impersonate。代码如下所示(抱歉,我没有SQL服务器来尝试您的确切方案)...

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
}

我在Directory.GetDirectories(UNCPath)中使用了这个,其中路径通向另一个域上的计算机并且它在那里工作。我还没有尝试过实施“runas”。

我称之为......

using(var login = new Login("myname","mydomain))
{
    login.AccessShare("mypassword");
    // do stuff
}

也许你可以根据你的问题进行调整。 LMK

答案 2 :(得分:0)

我尝试了所有可以找到的各种用户模拟代码示例。他们都没有工作。

最后,我想出了以下代码。它使用cmd.exe参数/C执行Carries out the command specified by string and then terminates。我执行的命令是runas /netonly ...

注意事项

不幸的是,必须手动输入密码。我的下一步是研究将按键发送到process。我尝试重定向标准输入并将其写入,但是没有用。我在某处读到,大多数密码提示仅接受直接来自键盘的输入。

此外,当SSMS打开时,连接到服务器对话框将显示您当前的域\用户名,但是它将使用您提供给runas的用户名进行身份验证。

最后,如果您的AD帐户已锁定,则在尝试连接到SQL Server之前不会收到错误消息。我忽略了复制收到的错误消息,但没有提及该帐户已被锁定。

代码

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