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 文章,其中详细介绍了该主题:
虽然权限 可能 是个问题,但您似乎确实在错误地调用 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
而不是 string
和 SqlInt32
而不是 int
。所有 Sql*
类型都有一个 .Value
属性,return 是预期的 .NET 本机类型。因此,您可以按如下方式使用它:
Arguments = "/C " + cmd.Value
我编写了一个 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 文章,其中详细介绍了该主题:
虽然权限 可能 是个问题,但您似乎确实在错误地调用 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
而不是 string
和 SqlInt32
而不是 int
。所有 Sql*
类型都有一个 .Value
属性,return 是预期的 .NET 本机类型。因此,您可以按如下方式使用它:
Arguments = "/C " + cmd.Value