Calling a SSIS Package from a Stored Procedure
July 4, 2011 2 Comments
Problem
Calling a SQL Server Integration Services (SSIS) package from s stored procedure should be an easy task however Microsoft has provided no direct method to do so.
A quick search on Google found this CodePlex example which uses the xp_cmdshell extended stored procedure to execute dtexec via the Windows Command Shell and this blog post (Rick Weyenberg) which describes how to use a stored procedure to create a SQL Server Job which executes a SSIS package via a Job Step.
Although the above examples work quite well, I wasn’t too keen on using xp_cmdshell and I wanted a solution that provided the ability to:
- Notify of execution failure
- Use dynamic package declaration
- Use dynamic variable assignment
Solution
This solution is based on the SQL Server Job example provided by Rick Weyenberg however it uses XML to implement my additional requirements.
It has been been tested on SQL Server 2005 – 2008(R2) and is configured to execute SSIS packages that reside on the same server as the executing stored procedure.
MSDN Gallery: http://code.msdn.microsoft.com/Calling-a-SSIS-Package-a35afefb
XML Structure
The structure of the XML used in this solution is as follows:
<package name="PackageName" notifyMethod="Method" notifyOperator="Operator"
owner="Owner" runAs="RunAs">
<variable name="Variable1" value="Value"/>
<variable name="Variable2" value="Value"/>
</package>
Package Element
- name: Name of the SSIS package to execute. You need to specify the full package name and path i.e. if a package called SSISTest resides in a folder called Test within MSDB, the package name would be \Test\SSISTest
- notifyMethod: The method used to notify of job failure. The value for this attribute corresponds with SQL Server Agent notification types (Except for “Automatically Delete Job” which is always set to “When the job completes” and “Write to the Windows Application Event Log” which is always set to “When the job fails”) . Leave blank for no notification.
- notifyOperator: The name of the SQL Server Operator to notify when notifyMethod is provided.
- owner: An account in SQL Server that has sufficient permissions to execute jobs
- runAs: An proxy account that has sufficient permissions to execute any tasks within the SSIS package. Leave blank to run under the SQL Server Agent Service Account.
I have only added the owner and runAs attributes to demonstrate that this can be done. I recommend that you do not allow the caller of the stored procedure to nominate which accounts to use.
Variable Element
- name: Name of the SSIS variable you want to assign a value to
- value: Value you want to assign to the variable
Stored Procedure
The stored procedure used to implement this solution is quite lengthy so I’ll break it down into chunks and provide explanations where required.
1) Create the stored procedure shell which accepts a parameter of type XML and implements some basic error handling.
The error handling implemented here only relates to job creation. Any errors that occur during job execution will be logged in the Windows Application Event Log.
CREATE PROCEDURE [dbo].[usp_ExecuteSSISPackage]
(
@xVar XML
)
AS
BEGIN
-- Step 2 code goes here
BEGIN TRY
-- Step 3 code goes here
-- Step 4 code goes here
-- Step 5 code goes here
-- Step 6 code goes here
-- Step 7 code goes here
-- Return successfull job creation
RETURN 1
END TRY
BEGIN CATCH
-- Retrieve error details
SELECT ERROR_NUMBER() AS ErrorNumber,ERROR_MESSAGE() AS ErrorMessage;
-- Return failed job creation
RETURN 0
END CATCH
END
2) Create some local variables.
@VariableTable is used to hold the shredded XML variable data (@xVar parameter)
-- Job specific variables
DECLARE @Server NVARCHAR(100)
DECLARE @JobName NVARCHAR(128)
DECLARE @JobId BINARY(16)
DECLARE @StepCmd VARCHAR(MAX)
-- Used to create SSIS variable mappings
DECLARE @MaxID BIGINT
DECLARE @CurrentID BIGINT
DECLARE @VariableList VARCHAR(MAX)
DECLARE @VariableTable TABLE
(
ID BIGINT IDENTITY(1,1),
Name VARCHAR(50),
Value VARCHAR(255)
)
-- Attribute Variables
DECLARE @PackageName VARCHAR(255)
DECLARE @NotifyMethod VARCHAR(10)
DECLARE @NotifyOperator VARCHAR(255)
-- ***********************************************
-- Please Note: I have only added the owner
-- and runAs attributes to demonstrate that this
-- can be done. I recommend that you DO NOT allow
-- the caller of the stored procedure to assign
-- which accounts to use.
DECLARE @Owner VARCHAR(100)
DECLARE @RunAsAccount VARCHAR(100)
-- ***********************************************
3) Shred the XML contained in the @xVar parameter and store the resulting data in local variables
-- Get package attributes
SELECT @PackageName = nref.value('@name[1]','varchar(255)'),
@NotifyMethod = nref.value('@notifyMethod[1]', 'varchar(10)'),
@NotifyOperator = nref.value('@notifyOperator[1]', 'varchar(255)'),
@Owner = nref.value('@owner[1]','varchar(100)'),
@RunAsAccount = nref.value('@runAs[1]', 'varchar(100)')
FROM @xVar.nodes('/package') AS R(nref)
-- Store SSIS variable details in local table variable
INSERT INTO @VariableTable
SELECT nref.value('@name[1]', 'varchar(50)'),
nref.value('@value[1]', 'varchar(50)')
FROM @xVar.nodes('/package/variable') AS R(nref)
4) Create the job.
The job name (@JobName) is made up of the combination of the package name and a GUID. This enables multiple copies of the same package to be executed at the same time.
@delete_level = 3 causes the job to be deleted regardless of whether job execution succeeded
-- Get the current server name
SET @Server = CONVERT(sysname, SERVERPROPERTY(N'servername'))
-- Create a unique job name
SET @JobName = @PackageName + '_' + CAST(NEWID() AS CHAR(36))
-- Create the job
IF @NotifyMethod = 'E-mail'
BEGIN
EXEC msdb.dbo.sp_add_job @job_name = @JobName, @enabled = 1,
@notify_level_email = 2, -- When the job fails
@notify_email_operator_name = @NotifyOperator,
@notify_level_eventlog = 2, -- When the job fails
@Owner_login_name = @Owner,
@delete_level = 3, -- Delete job regardless of execution state
@job_id = @JobId OUTPUT
END
ELSE IF @NotifyMethod = 'Page'
BEGIN
EXEC msdb.dbo.sp_add_job @job_name = @JobName, @enabled = 1,
@notify_level_page = 2, -- When the job fails
@notify_page_operator_name = @NotifyOperator,
@notify_level_eventlog = 2, -- When the job fails
@Owner_login_name = @Owner,
@delete_level = 3, -- Delete job regardless of executionstate,
@job_id = @JobId OUTPUT
END
ELSE IF @NotifyMethod = 'Net send'
BEGIN
EXEC msdb.dbo.sp_add_job @job_name = @JobName, @enabled = 1,
@notify_level_netsend = 2, -- When the job fails
@notify_netsend_operator_name = @NotifyOperator,
@notify_level_eventlog = 2, -- When the job fails
@Owner_login_name = @Owner,
@delete_level = 3, -- Delete job regardless of execution state,
@job_id = @JobId OUTPUT
END
ELSE
BEGIN
EXEC msdb.dbo.sp_add_job @job_name = @JobName, @enabled = 1,
@notify_level_eventlog = 2, -- When the job fails
@Owner_login_name = @Owner,
@delete_level = 3, -- Delete job regardless of execution state,
@job_id = @JobId OUTPUT
END
-- Add the job to the server
EXEC msdb.dbo.sp_add_jobserver @job_name = @JobName, @server_name = @Server
5) Get the package variables and their associated values.
This code portion loops through the rows in the @VariableTable and builds a string which is used to add the variables and their associated values to the job step. The syntax used to set package variables is:
/SET \Package.Variables[User::VariableName].Value;VariableValue
e.g.
/SET \Package.Variables[User::ProjectID].Value;666
Actual stored procedure code.
-- Build the SSIS variable assignment list SELECT @MaxID = MAX(ID) FROM @VariableTable SET @VariableList = ''
SET @CurrentID = 0 WHILE (@CurrentID <= @MaxID) BEGIN SELECT @VariableList = @VariableList + ' /SET \Package.Variables[User::' + Name + '].Value;' + Value FROM @VariableTable WHERE ID = @CurrentID SET @CurrentID = @CurrentID + 1 END
6) Create the job step
-- Create the job step
SET @StepCmd = '/SQL "' + @PackageName + '" /SERVER '
+ @Server + ' /CHECKPOINTING OFF ' + @VariableList + ' /REPORTING E'
EXEC msdb.dbo.sp_add_jobstep @job_name = @JobName,
@step_name = N'SSISExecuteStep', @step_id = 1,
@cmdexec_success_code = 0, @on_success_action = 1,
@on_fail_action = 2, @retry_attempts = 0,
@retry_interval = 0, @os_run_priority = 0,
@subsystem = N'SSIS', @command = @StepCmd,
@database_name = N'master', @flags=0, @proxy_name=@RunAsAccount
7) Start the job
-- Start job
EXEC msdb.dbo.sp_start_job @job_id = @JobId
That’s it. To execute the stored procedure in SSMS use:
DECLARE @XML XML
SET @XML =
'<package name="\SSISTest\SSISTest" notifyMethod="E-mail" notifyOperator="ITSupport"
owner="Machine\Administrator" runAs="">
<variable name="TestID" value="2"/>
<variable name="ModifiedBy" value="Jeff"/>
</package>'
exec [dbo].[usp_ExecuteSSISPackage] @XML
To call the stored procedure from .Net is very straight forward.
I’m using the Microsoft Enterprise Library 5.0 as it handles the majority of the database code required to connect to SQL Server and execute the stored procedure.
using Microsoft.Practices.EnterpriseLibrary.Data; using Microsoft.Practices.EnterpriseLibrary.Data.Sql; using System.Data.Common; using System; using System.Data; class SSIS { public int ExecuteSSISPackage() { SqlDatabase db = (SqlDatabase)DatabaseFactory.CreateDatabase(); // This is the stored procedure we created earlier DbCommand dbCommand = db.GetStoredProcCommand("[dbo].[usp_ExecuteSSISPackage]"); // Add a paramter of type XML and assign the String xmlData = "<Your XML Data Goes Here>";
db.AddInParameter(dbCommand, "@xVar", DbType.Xml, xmlData); // Add a ReturnValue paramter of type INT for error checking db.AddParameter(dbCommand, "@RETURN_VALUE", DbType.Int16, 0, ParameterDirection.ReturnValue, false, 0, 0, "", DataRowVersion.Current, 0); // Execute the stored procedure DataSet ds = db.ExecuteQuery(dbCommand); //Get error indicator int erroCode = Convert.ToInt32(db.GetParameterValue(dbCommand, "@RETURN_VALUE")); return erroCode; } }
Conclusion
In this post I have demonstrated how to call a SSIS package from a SQL Server stored procedure.
Shortly I will demonstrate how to achieve the same result in Denali. In the meantime, have a read of the following post by Jamie Thomson (SSIS Junkie) – Synchronous execution of SSIS packages using T-SQL : Denali
