SSIS包调用WinSCP C#脚本上传SFTP报UnsafeInvokeInternal错误求助
SSIS脚本SFTP上传UnsafeInvokeInternal错误修复
问题现象
在SSIS脚本任务中使用C#调用WinSCP实现文件从SQL Server导出后上传到SFTP目录时,运行抛出System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters)错误,尝试通过gacutil注册WinSCP程序集未解决问题。
核心原因
该错误是SSIS脚本任务反射调用内部方法时捕获异常的外层包装,不是根本故障点,实际触发原因集中在以下几类:
- GAC注册命令输入错误:执行注册时输入的程序集名为
WinSCPnet.dl,缺失后缀最后一个字符,实际未完成程序集注册 - WinSCP组件不匹配:WinSCPnet.dll .NET程序集与WinSCP.exe原生程序版本不一致,或程序位数(32/64位)与SSIS运行时位数不匹配,导致程序集加载失败
- 路径与权限问题:配置的WinSCP.exe存储在带访问限制的共享目录,SQL Server服务/代理运行账号无该目录的执行权限、源文件读取权限或SFTP目录写入权限
- 脚本逻辑漏洞:未区分密码认证与私钥认证场景,本地文件路径拼接未做规范化处理,路径错误导致上传失败
修复步骤
- 完成正确的GAC注册
选择与项目.NET Framework版本匹配的gacutil工具(当前环境使用NETFX 4.6.1版本工具即可),执行正确的注册命令,注意核对程序集文件名完整:
注册成功后工具会返回程序集添加到全局缓存成功的提示。注意选择对应.NET Framework 4.0构建的WinSCPnet.dll版本,不要使用.NET Core/.NET Standard版本的程序集。"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\gacutil.exe" /i WinSCPnet.dll - 统一WinSCP组件版本与部署位置
- 配套使用同版本的WinSCPnet.dll和WinSCP.exe,禁止跨版本混用
- 根据SSIS项目的运行时位数选择对应程序:项目开启64位运行时则使用64位WinSCP.exe,开启32位兼容模式则使用32位版本
- 将两个文件统一部署到服务器本地固定目录(如
C:\Program Files\WinSCP\),不要放在带特殊权限限制的管理共享目录,给SQL Server数据库引擎服务、SQL Server代理服务的运行账号授予该目录的读取、执行权限。
- 修正脚本逻辑问题
原脚本存在认证逻辑缺失、路径拼接不规范的问题,替换核心执行代码如下:public void Main() { String HostName = (String)Dts.Variables["User::HostName"].Value; int PortNumber = (int)Dts.Variables["User::PortNumber"].Value; String Username = (String)Dts.Variables["User::Username"].Value; String Password = (String)Dts.Variables["User::Password"].Value; String FileLocation = (String)Dts.Variables["User::FileLocation"].Value; String FileName = (String)Dts.Variables["User::FileName"].Value; String RemoteLocation = (String)Dts.Variables["User::RemoteLocation"].Value; String SshPrivateKeyPath = (String)Dts.Variables["User::SshPrivateKeyPath"].Value; try { SessionOptions sessionOptions = new SessionOptions { Protocol = Protocol.Sftp, HostName = HostName, UserName = Username, PortNumber = PortNumber, }; // 区分私钥认证和密码认证场景 if(!string.IsNullOrEmpty(SshPrivateKeyPath)) { sessionOptions.GiveUpSecurityAndAcceptAnySshHostKey = true; sessionOptions.SshPrivateKeyPath = SshPrivateKeyPath; sessionOptions.PrivateKeyPassphrase = Password; } else { sessionOptions.Password = Password; } using (Session session = new Session()) { // 指向本地部署的WinSCP.exe路径 session.ExecutablePath = @"C:\Program Files\WinSCP\WinSCP.exe"; // 配置会话日志路径,方便排查连接、传输问题 session.SessionLogPath = @"C:\Logs\WinSCP\sftp_transfer.log"; session.Open(sessionOptions); TransferOptions transferOptions = new TransferOptions(); transferOptions.TransferMode = TransferMode.Binary; // 规范化拼接本地文件路径,避免缺失分隔符导致路径错误 string localFilePath = Path.Combine(FileLocation, FileName); TransferOperationResult transferResult; transferResult = session.PutFiles(localFilePath, RemoteLocation, false, transferOptions); transferResult.Check(); bool fireAgain = false; foreach (TransferEventArgs transfer in transferResult.Transfers) { Dts.Events.FireInformation(3, "sFTP", "Upload Succeed", transfer.FileName, 0, ref fireAgain); } Dts.TaskResult = (int)ScriptResults.Success; } } catch (Exception e) { Dts.Events.FireError(18, "sFTP Upload Error: {" + HostName + "}", e.ToString(), "", 0); Dts.TaskResult = (int)ScriptResults.Failure; } } - 部署前验证
- 提前创建会话日志目录,给服务运行账号授予写入权限,运行失败时直接查看WinSCP会话日志即可定位具体错误点
- 在服务器上手动使用配置的账号信息测试SFTP连接,确认网络连通性、账号权限、远程路径均正常
- 本地调试时将Visual Studio中SSIS项目的
Run64BitRuntime属性与服务器生产环境的运行时配置对齐,调试通过后再部署包
内容的提问来源于stack exchange,提问作者Ricardo Ferreira
相关产品推荐
相关产品推荐

