SQL Server 2022 中 EzApi 创建和保存 SSIS 包报错,如何解决?
结论
先保留 Microsoft.Pipeline、Microsoft.ExecuteSQLTask 等原有标识,排查 SSIS 运行环境、程序集加载和组件注册。现有现象不足以证明 SQL Server 2022 要求将这些 moniker 全部替换为完整程序集名称或带 .8 的标识。
下面这行原生 API 调用也会失败:
pkg0.Executables.Add("Microsoft.Pipeline");
说明不使用 EzApi 时,故障同样会出现。应先用原生 API 完成创建、保存和重新加载包的最小测试,再调整 EzApi。
目前还无法确认以下判断:
0x80070057的根因一定是 moniker 变化。Microsoft.SqlServer.PipelineXML要求使用另一套保存专用 moniker。- Microsoft 因安全原因禁止普通应用程序使用 ScriptTask DLL。
题目没有提供最终修复结果,因此无法断言替换某个 DLL 或修改某个字符串就能解决问题。
为什么替换 moniker 后仍然会失败
SSIS 创建任务需要解析任务名称、加载程序集,并创建原生组件。将短名称改成完整程序集名称,可能让某个任务通过另一条加载路径创建成功。但这既不能证明原来的名称已经失效,也不能说明后续依赖都已正确加载。
如果空 Data Flow 能保存,加入组件后却无法保存,就需要检查加入组件后触发的处理,包括组件持久化、依赖加载和版本一致性。仅凭异常涉及哪个 DLL,还无法判断根因。
这两个错误需要分别排查:
| 错误 | 当前能确定的范围 |
|---|---|
Cannot create a task from XML ... 0x80070057 | 任务创建或初始化失败;错误码本身不能指明哪个名称、文件或依赖有问题 |
Failed to save Data Flow objects | 保存数据流对象时失败;需要查看更具体的内部异常和 SSIS 错误信息 |
0x80070057 对应参数错误,但这里的“参数”未必就是传给 Executables.Add 的字符串。
先核对实际加载的 SSIS 环境
检查安装与进程位数
确认生成程序所在的机器安装了所需的 SQL Server 2022 Integration Services 运行组件。仅安装数据库引擎、SSMS,或只引用几个 DLL,都不能替代完整的 SSIS 依赖环境。
还需要检查:
- 生成程序实际运行在 x86 还是 x64。
- 对应位数的 SSIS 组件及相关提供程序是否存在。
- 是否同时安装了多个 SQL Server 或 SSIS 版本。
- 应用程序输出目录是否残留 SQL Server 2016 等旧版本的 DLL。
- 是否引用了 Visual Studio 扩展目录中的设计时文件,却加载了另一套运行时文件。
排查实际加载情况时,调试器的“模块”窗口比项目的“引用”列表更有用:引用列表显示编译时使用的文件,模块窗口显示进程实际加载的文件。
可以先输出以下信息:
using System;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
Console.WriteLine("64-bit process: " + Environment.Is64BitProcess);
Console.WriteLine(typeof(Application).Assembly.FullName);
Console.WriteLine(typeof(Application).Assembly.Location);
Console.WriteLine(typeof(MainPipe).Assembly.FullName);
Console.WriteLine(typeof(MainPipe).Assembly.Location);
这些输出只覆盖对应的托管程序集或互操作程序集。原生 SSIS DLL 的实际加载路径和文件版本,还需要在模块窗口中检查。
核对环境是否一致时,要一起检查程序集版本、文件版本和实际加载路径,不能只看 Version=16.0.0.0。
检查目标版本
如果使用 SSIS 项目做对照测试,应确认项目的 TargetServerVersion 为 SQL Server 2022。生成程序使用的 SDK 如果提供目标版本设置,也要核对。
Visual Studio 能成功保存包,说明它自身的进程和依赖环境可以完成操作,并不能证明独立 C# 程序加载了同一套组件。
如何检查 moniker
使用 Application.TaskInfos 检查任务,使用 Application.PipelineComponentInfos 检查数据流组件:
var app = new Microsoft.SqlServer.Dts.Runtime.Application();
foreach (var task in app.TaskInfos)
{
Console.WriteLine(
$"Task: {task.Name}, CreationName: {task.CreationName}");
}
foreach (var component in app.PipelineComponentInfos)
{
Console.WriteLine(
$"Component: {component.Name}, CreationName: {component.CreationName}");
}
这些代码必须在发生故障的生成程序中执行,结果才反映该进程看到的组件信息。
使用这些名称时,需要区分:
Executables.Add(...)接收任务的创建标识。ComponentClassID用于指定数据流组件,不能直接套用任务的程序集名称写法。- 不能仅凭
.5、.8等后缀中的数字推断 SQL Server 产品版本。 - 能枚举到组件,不代表它一定能完成实例化、保存和重新加载。
可以把枚举得到的 CreationName 作为诊断候选,与原有标识分别测试。每次测试都使用新进程和新包,并记录成功到了哪一步。批量替换会让结果失去对照依据。
用原生 API 分阶段复现
先移除 EzApi 和 ScriptTask,只保留 Data Flow。下面的示例用于判断保存空数据流时是否失败,以及故障是否在加入 OLE DB Source 后才出现。它尚未被验证为适用于所有 SQL Server 2022 安装环境的修复代码。
using System;
using System.IO;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
sealed class DiagnosticEvents : DefaultEvents
{
public override bool OnError(
DtsObject source,
int errorCode,
string subComponent,
string description,
string helpFile,
int helpContext,
string idofInterfaceWithError)
{
Console.Error.WriteLine(
$"0x{unchecked((uint)errorCode):X8} " +
$"[{subComponent}] {description}");
return false;
}
}
static class Program
{
static void Main()
{
var app = new Application();
var events = new DiagnosticEvents();
var package = new Package
{
Name = "TestPackage"
};
try
{
Console.WriteLine("Creating Data Flow");
var host =
(TaskHost)package.Executables.Add("Microsoft.Pipeline");
host.Name = "df0";
string emptyPath =
Path.Combine(Path.GetTempPath(), "ssis-empty-flow.dtsx");
app.SaveToXml(emptyPath, package, events);
Console.WriteLine("Saved: " + emptyPath);
Console.WriteLine("Adding OLE DB Source");
var dataFlow = (MainPipe)host.InnerObject;
var metadata = dataFlow.ComponentMetaDataCollection.New();
metadata.ComponentClassID = "Microsoft.OLEDBSource";
IDTSDesigntimeComponent100 instance = metadata.Instantiate();
instance.ProvideComponentProperties();
metadata.Name = "TestOleDbSource";
metadata.ValidateExternalMetadata = false;
string componentPath =
Path.Combine(Path.GetTempPath(), "ssis-with-source.dtsx");
app.SaveToXml(componentPath, package, events);
Console.WriteLine("Saved: " + componentPath);
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.ToString());
foreach (DtsError error in package.Errors)
{
Console.Error.WriteLine(
$"0x{unchecked((uint)error.ErrorCode):X8} " +
error.Description);
}
throw;
}
}
}
这个 OLE DB Source 尚未配置连接和查询,不能作为可执行的数据抽取包。测试只覆盖创建和持久化阶段,不涉及执行阶段。
根据失败位置继续排查:
Executables.Add失败:优先检查任务名称解析、运行时加载、安装与注册。- 空 Data Flow 保存成功,
Instantiate或ProvideComponentProperties失败:检查具体组件及其依赖。 - 加入组件后才保存失败:检查完整事件日志、组件持久化相关依赖,以及是否混用了不同版本的文件。
- 保存成功:通过
Application.LoadPackage重新加载,再另行配置连接、验证和执行。
DelayValidation 和 ValidateExternalMetadata = false 无法修复程序集加载、组件注册或序列化故障。
如何定位 Failed to save Data Flow objects
排查时需要收集以下信息,仅有最外层的异常消息还不够:
ex.ToString()中的内部异常和完整调用堆栈。- 保存时传入的
IDTSEvents回调信息。 Package.Errors中的错误。- 故障进程实际加载的 DLL 路径和版本。
如果这些信息仍不足以定位问题,可以继续检查:
- 使用程序集绑定日志检查托管程序集绑定失败的情况。
- 使用 Process Monitor 检查故障时是否查找了错误版本的 DLL,或访问了不存在的文件、注册表项。
判断是否缺少依赖、加载了旧版本或安装注册不完整,需要这些诊断结果支持。调用堆栈中出现 Microsoft.SqlServer.PipelineXML,只能说明失败发生在相关调用过程中。
如果确认安装或注册有问题,应使用对应版本的安装程序修复。没有明确依据时,不要手工复制系统 DLL、修改注册表,或反复尝试注册不同版本的组件。
运行时与保存时是否使用不同 moniker
创建接口接受的标识与 DTSX 中最终持久化的标识,不一定逐字相同。二者有差异,并不足以说明必须在保存前切换 moniker。
Visual Studio 生成的 XML 仍使用原有名称,也不能证明 PipelineXML 只接受旧名称。还需要在相同目标版本下比较实际加载的环境,并检查包能否完成保存和重新加载。
不建议在保存前手工改写 CreationName 或 ComponentClassID,也不建议通过修改 DTSX 字符串绕过当前异常。
原生 API 测试通过后,再调整 EzApi:先统一引用和依赖来源,再逐项验证任务与组件标识。某项替换只有在创建、保存、重新加载及后续验证中都能稳定通过,才适合保留在兼容性代码中。
ScriptTask 的异常需要单独处理
下面的异常涉及 VSTA 宿主和路径布局:
Except devenv, all applocalized program like dtdebughost.exe
should be under CommonExtensions folder
这条异常不能证明 Microsoft 因代码安全问题禁止第三方程序使用 ScriptTask DLL。
需要单独检查 VSTA、SSIS 设计工具版本、ProjectTemplatePath,以及程序加载的设计时依赖。即使任务实例创建成功,独立程序也未必具备创建、编译脚本项目所需的完整环境。
可以暂时将 ScriptTask 移出最小复现。删除 LoadNewProject 并不代表脚本任务已经适配成功,后续仍需验证脚本项目、编译产物和包加载行为。
备注:内容仅供参考。
