CLR 存储过程执行命令

CLR Stored procedure to execute command

我编写了一个 CLR 存储过程来执行作为参数传递给它的命令。这是 CLR 存储过程的代码;我在 SQL 服务器中将程序集注册为 "unsafe" 并从中创建了存储过程。

[SqlProcedure]
public static int ExecuteCommand(string cmd)
{
    int success = 0;

    try
    {
        EventLog.WriteEntry("MyAppName", "Starting execution " + cmd + " Username: " + System.Security.Principal.WindowsIdentity.GetCurrent().Name + " OR " + Environment.UserName, EventLogEntryType.Information);

        Process p = new Process();
        p.StartInfo = new ProcessStartInfo() { FileName = "cmd.exe", Arguments = cmd, WindowStyle = ProcessWindowStyle.Hidden};
        p.Start();
    }
    catch (Exception ex)
    {
        EventLog.WriteEntry("MyAppName", "Executed command : " + cmd + "  with error : " + ex.Message, EventLogEntryType.Error);
        success = 1;
    }

    return success;
}

此代码能够写入事件查看器并将用户名打印为 NT AUTHORITY\SYSTEM。然后它启动 cmd.exe,但它不执行传递的命令(例如 mkdir)并且 cmd.exe 保持在 运行ning 状态。在任务管理器中,我可以看到每个 运行 存储过程的多个 cmd.exe 实例,但在用户名列中没有值。 SQL 服务器服务是 运行 本地系统帐户。

这里出了什么问题?如果与权限有关,那么有没有办法在调用存储过程的用户的上下文中执行CLR存储过程?

听起来像是假冒问题,或者更确切地说是缺乏假冒问题。 SQL CLR 不会将您的令牌(凭据)转发给其他应用程序,除非您明确告诉它这样做。你可以尝试这样的事情:

[SqlProcedure]
public static int ExecuteCommand(string cmd)
{
    int success = 0;
    Impersonate impersonatedUser = new Impersonate();
    try
    {
        EventLog.WriteEntry("MyAppName", "Starting execution " + cmd + " Username: " + System.Security.Principal.WindowsIdentity.GetCurrent().Name + " OR " + Environment.UserName, EventLogEntryType.Information);
        Process p = new Process();
        p.StartInfo = new ProcessStartInfo() { FileName = "cmd.exe", Arguments = cmd, WindowStyle = ProcessWindowStyle.Hidden };
        p.Start();

    }
    catch (Exception ex)
    {
        EventLog.WriteEntry("MyAppName", "Executed command : " + cmd + "  with error : " + ex.Message, EventLogEntryType.Error);
        success = 1;
    }
    finally
    {
        impersonatedUser.Undo();
    }
    return success;
}

注意只有几行不同。

Impersonate impersonatedUser = new Impersonate();

这个告诉 SQL 模拟正在执行存储过程的用户。这将因 SQL 身份验证而失败,因为 SQL 身份验证实际上不是 windows 用户。

finally
{
     impersonatedUser.Undo();
}

如果您无法还原模拟,可能会导致其他问题,因此无论如何将其放在 finally 块中都会使模拟还原。

这是一篇关于该主题的 Microsoft 文章,其中详细介绍了该主题:

https://msdn.microsoft.com/en-us/library/ms345105.aspx

虽然权限 可能 是个问题,但您似乎确实在错误地调用 cmd.exe。如果你简单地称它为:

cmd.exe mkdir folder_name

然后您将得到您所看到的行为,即未创建文件夹并且进程继续而不退出。

您需要使用 /C 命令调用 cmd.exe 将命令传递给 运行 -行开关,它指示 CMD 执行提供的命令然后退出。因此,调用以下内容:

cmd.exe /C mkdir folder_name

会像您期望的那样工作。所以也许你需要使用:

Arguments = "/C " + cmd

此外,您可能需要另一个 "WindowStyle" and/or 其他 ProcessStartInfo 属性。如果以上方法不起作用,我会检查我过去使用过的东西,因为我已经让它起作用了。

P.S。您应该为参数使用 Sql* 类型,为 SQLCLR 方法使用 return 值。所以使用 SqlString 而不是 stringSqlInt32 而不是 int。所有 Sql* 类型都有一个 .Value 属性,return 是预期的 .NET 本机类型。因此,您可以按如下方式使用它:

Arguments = "/C " + cmd.Value