c# - 从 SQLCLR 调用时,带有 Remove=true 的 WinSCP Session.PutFiles 获取 "Error deleting file System Error. Code: 5"但在 SSIS 脚本任务中工作

标签 c# sql-server sqlclr winscp winscp-net

我正在创建一个程序集文件,以便 get 文件和 put 文件可以作为存储过程从 SQL Server 中调用。下面是放置文件的方法。当我传递 SqlBoolean DeleteLocalFolder=true 时,WinSCP 出错并显示以下错误消息:

Error: WinSCP.SessionRemoteException: Error deleting file System Error. Code: 5

[SqlProcedure]
public static void PutFiles(SqlString HostName, SqlString UserName, SqlString Password,
                            SqlString SshHostKeyFingerprint, SqlString SshPrivateKeyPath, 
                            SqlString SshPrivateKeyPassphrase, SqlString LocalFolderPath,
                            SqlString RemoteFolderPath,SqlBoolean DeleteLocalFolder,
                            SqlString FileMask, out SqlString strMessage)
{
    // Declare Variables to hold the input parameter values
    string hostName = HostName.Value;
    string userName = UserName.Value;
    string password = Password.Value;
    string sshFingerPrint = SshHostKeyFingerprint.Value;
    string sshPrivateKey = SshPrivateKeyPath.Value;
    string sshPassPhrase = SshPrivateKeyPassphrase.Value;

    bool delateLocalFolder = DeleteLocalFolder.Value;
    //Local Directory 
    DirectoryInfo dir = new DirectoryInfo(LocalFolderPath.Value);
    string folderName = dir.Name;

    //Begin connection to SFTP **Always uses default SFTP port 
    try
    {
        string FtpFolderPath = RemoteFolderPath.Value;

        SessionOptions options = new SessionOptions()
        {
            Protocol = Protocol.Sftp,
            HostName = hostName,
            UserName = userName,
            Password = password,
            SshHostKeyFingerprint = sshFingerPrint,
            SshPrivateKeyPath = sshPrivateKey,
            SshPrivateKeyPassphrase = sshPassPhrase                          
        };

        //Open the Remote session
        using (Session session = new Session())
        {
            session.ExecutablePath = ExecutablePath+"WinSCP.exe";
            session.SessionLogPath = ExecutablePath+"WinscpLog.log";
            session.DisableVersionCheck = true;
            session.Open(options);

            session.ExecutableProcessUserName = "username to log into the sql server instance";

            SecureString _Password = new SecureString();
            foreach (char _PasswordChar in "password to log in sql instance")
            {
                _Password.AppendChar(_PasswordChar);
            }
            session.ExecutableProcessPassword = _Password;
            // Upload files
            TransferOptions transferOptions = new TransferOptions();
            transferOptions.TransferMode = TransferMode.Binary;
            transferOptions.FileMask = FileMask.Value;

            TransferOperationResult transferoperationalResult;

            transferoperationalResult = session.PutFiles(LocalFolderPath.Value, FtpFolderPath, false, transferOptions);
            transferoperationalResult.Check();

            if (transferoperationalResult.IsSuccess)
            {
                if (dir.Exists == true)
                {
                    string sourcePath = FtpFolderPath + folderName + "//";
                    session.MoveFile(sourcePath + "*.*", FtpFolderPath); 
                    session.RemoveFiles(sourcePath);
                }
            }
        }

        strMessage = "Upload of files successful";
    }
    catch (Exception e)
    {
        //Console.WriteLine("Error: {0}", e);
        strMessage = "Error: "+ e;
    }
}

我在 SSIS 脚本任务中编写了自定义代码并调用了 Session.PutFiles method通过传递参数 bool remove = true。它可以正常工作。谁能解释为什么它只在自定义程序集中出错?

最佳答案

默认情况下,到达 SQL Server 外部的进程使用 MSSQLSERVER(或 MSSQL.InstanceName)服务的服务帐户的安全上下文。使用此默认设置时,此服务帐户需要对文件所在的文件夹具有写入/修改权限(如果文件权限使用继承,则可能对文件本身具有写入/修改权限)。

或者,您可以使用模拟将外部进程的安全上下文更改为执行存储过程的登录(在 SQL Server 中)的安全上下文,但前提是登录是 Windows 登录。 SQL Server 登录没有操作系统已知的 SID,因此它们的安全上下文将使用默认值,这同样是主 SQL Server 数据库引擎进程的服务帐户。

using System.Security.Principal;

public class stuff
{
   [SqlProcedure]
   public static void PutFiles()
   {

      using (WindowsImpersonationContext __ImpersonationIdentity =
                   SqlContext.WindowsIdentity.Impersonate())
      {
         ... {do stuff} ...

         __ImpersonationIdentity.Undo();
      }
   }
}

此外,Martin Prikryl 对此答案发表评论时提到:

WinSCP .NET assembly supports impersonation on its own. See Session.ExecutableProcessUserName and Session.ExecutableProcessPassword

想法是(我假设,虽然我没有测试过):

using System.Security;

  ...

   using (Session session = new Session())
   {
     session.ExecutableProcessUserName = "some_user_name";

     SecureString _Password = new SecureString();
     foreach (char _PasswordChar in "some_password")
     {
        _Password.AppendChar(_PasswordChar);
     }
     session.ExecutableProcessPassword = _Password;
     ... other session stuff...
     session.Open(options);

       ...more stuff...

     _Password.Dispose();
   } /* using() */

在上面的示例中,用户名和密码是字符串文字,但实际上这些值可以来自存储过程的输入参数,甚至可以来自 sqlservr.exe.Config 文件。或者由于 Assembly 有一个 UNSAFEPERMISSION_SET 以便引用 COM 对象,您实际上可以从任何地方获取值,包括注册表等。

所以也许这是值得尝试的事情。我不确定什么时候生效,所以尝试一个选项,如果它不起作用,然后尝试另一个。

请注意,至少,选项之间的区别是:

  1. 如我为上面提供的代码那样,通过 SQLCLR 使用模拟仅允许模拟调用登录名(并且仅当该登录名是 Windows 登录名时),而不是任何随机登录名。但是,这也不需要密码。
  2. 使用 WinSCP 进行模拟可以灵活地模拟登录名/帐户,但需要输入密码。
  3. WinSCP 模拟将允许 SQL Server 登录使用模拟,因此这是一个有趣的选项

关于c# - 从 SQLCLR 调用时,带有 Remove=true 的 WinSCP Session.PutFiles 获取 "Error deleting file System Error. Code: 5"但在 SSIS 脚本任务中工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52393447/

相关文章:

c# - 遍历页面上的所有用户控件

javascript - 客户端对表单的一部分进行验证

c# - SQL Server数据库专项查询

c# - SQL Server 管理对象 (SMO) 的默认约束不一致

sql-server - SQLCLR 静态类构造函数中的数据访问

sql-server - 如何在 CLR 触发器中获取当前正在执行的命令的 sql server 用户名?

c# - 如何以编程方式在悬停时在面板内显示图片框?

c# - 在表单上获取 url 的 asp.net mvc 强制样式

sql - 通过隐藏列进行分组 - SQL

c# - 访问从 SQL Server 中的 SQLCLR 存储过程返回的 SqlXml