具有Remove = true的WinSCP Session.PutFiles获取“删除文件系统错误时出错”。代码:从SQLCLR调用时为5“,但在SSIS脚本任务中有效

问题描述 投票:2回答:1

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

错误:WinSCP.SessionRemoteException:删除文件系统错误时出错。代码: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。它可以解决任何问题。任何人都可以解释为什么它只在自定义程序集中出错?

c# sql-server sqlclr winscp winscp-net
1个回答
1
投票

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

或者,您可以使用模拟将外部进程的安全上下文更改为执行存储过程的Login(在SQL Server中)的安全上下文,但前提是Login是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程序集支持自己的模拟。见Session.ExecutableProcessUserNameSession.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文件。或者由于大会有一个PERMISSION_SETUNSAFE以引用COM对象,你可以真正从任何地方获取值,包括注册表等。

所以也许这是尝试的东西。我不确定何时生效,所以尝试一个选项,如果它不起作用,那么尝试另一个。

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

  1. 通过SQLCLR模拟,因为我提供上面的代码只允许模拟调用登录(并且只有当登录是Windows登录时),而不是任何随机登录。但是,这也不需要密码。
  2. 使用WinSCP进行模拟可以灵活地模拟登录/帐户,但需要传递密码。
  3. WinSCP模拟将允许SQL Server登录使用模拟,因此这是一个有趣的选项
© www.soinside.com 2019 - 2024. All rights reserved.