Blog Post

Migrating SQL Agent Jobs with Schedules using T-SQL

,

I’ve seen many a blog post about migrating SQL Agent jobs across instances using PowerShell, SMO, T-SQL, and the like. One thing I have not found readily is the ability to create jobs while maintaining the scheduling in place. For those who may not be aware, the easiest way to accomplish this is by using the SSIS Transfer Jobs Task: https://technet.microsoft.com/en-us/library/ms137568(v=sql.120).aspx

I’m providing a T-SQL alternative here for three potential case scenarios:

  1. SSIS is not enabled in your environment
  2. You don’t have rights to perform the action yourself, and need to provide a script
  3. You’d like to override certain job properties to a hard coded value on the target instance (for example, setting the owner for all jobs to be ‘sa’)

I developed the T-SQL based on the code blocks created when you script a job directly through SQL Server Management Studio (SSMS), which also follows the required steps as outlined here: https://technet.microsoft.com/en-us/library/ms181153(v=sql.105).aspx (which, for some reason, Microsoft only references through version 2008 R2). Run the script against the instance that you want to copy the jobs and schedules from.

The script will read the job definitions from the msdb system tables and output a script that performs the following when executed against a different instance:

  1. Creates the job categories if they do not already exist
  2. Creates the appropriate schedules if an existing job schedule with the same name does not exist
  3. Creates the job if an existing job with the same name does not exist
  4. Creates the job steps for all jobs matching job names from the originating instance where the step id does not already exist
  5. Attaches schedules to all jobs matching the configuration in the originating instance
  6. Sets the job start step based on the configuration in the originating instance
  7. Sets the job server to (local) for all jobs

The short take away is that the script is designed to be executed against a target instance that does not have jobs or schedules with the same names as the source instance. If there are identical names, make sure that the definitions are identical as well prior to running the script against the target. I’m also assuming that all jobs are local.

Things to note before running:

  1. Create the appropriate SQL Agent operator on the target instance. The script will only set the operator based on the default you provide (set as “DBAdmins” by default). If you need different operators for different jobs, then you’ll have to extend the script. Here’s how to create an operator: https://msdn.microsoft.com/en-us/library/ms175962.aspx
  2. All jobs and schedules will be created with ‘sa’ as owner. If that doesn’t work for you, then you’ll have to do some modifications to the script.

Ok, I’m starting to feel like this is turning into a pharmaceutical drug ad with all the disclaimers. Here’s the script already:

Download: http://ow.ly/hVxg3005IxH

Hope this helps!

/********************************************************
Script to create job definitions along with schedules
for migrating across instances.
Created by Jared Zagelbaum, jaredzagelbaum.wordpress.com
Created 5/21/2015
Follow me on Twitter: @JaredZagelbaum
Run the script against the instance that you want to copy the jobs and schedules from.
The script will read the job definitions from the msdb system tables and output a script that performs the following when executed against a different instance:
Creates the job categories if they do not already exist
Creates the appropriate schedules if an existing job schedule with the same name does not exist
Creates the job if an existing job with the same name does not exist
Creates the job steps for all jobs matching job names from the originating instance where the step id does not already exist
Attaches schedules to all jobs matching the configuration in the originating instance
Sets the job start step based on the configuration in the originating instance
Sets the job server to (local) for all jobs 
The short take away is that the script is designed to be executed against a target instance that does not have jobs or schedules with the same names as the source instance. 
If there are identical names, make sure that the definitions are identical as well prior to running the script against the target. I'm also assuming that all jobs are local.
Things to note before running:
Create the appropriate SQL Agent operator on the target instance. The script will only set the operator based on the default you provide (set as "DBAdmins" by default). 
If you need different operators for different jobs, then you'll have to extend the script. Here's how to create an operator: https://msdn.microsoft.com/en-us/library/ms175962.aspx
All jobs and schedules will be created with "sa" as owner. If that doesn't work for you, then you'll have to do some modifications to the script. 
*********************************************************/USE [msdb]
GO
PRINT N'Use [msdb]'
/*******************************************
Create Job Categories
********************************************/PRINT N'--Create Job Categories'
PRINT char(13) + char(10)
PRINT N'DECLARE @ReturnCode INT';
PRINT N'SELECT @ReturnCode = 0';
DECLARE @categoryname sysname
Declare categorycursor CURSOR FAST_FORWARD FOR
SELECT name FROM msdb.dbo.syscategories WHERE category_class = 1
OPEN categorycursor
PRINT N'BEGIN TRANSACTION';
PRINT char(13) + char(10);
FETCH NEXT FROM categorycursor
 INTO @categoryname
  WHILE @@FETCH_STATUS = 0
 BEGIN
PRINT N'IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name= ' + char(39) + @categoryname  + char(39) + N' AND category_class=1)';
PRINT N'BEGIN'
PRINT N'EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N''JOB'', @type=N''LOCAL'', @name=N''' + @categoryname + char(39);
PRINT N'IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback';
PRINT N'END'
PRINT char(13) + char(10);
FETCH NEXT FROM categorycursor
 INTO @categoryname
END
CLOSE categorycursor;
DEALLOCATE categorycursor;
PRINT char(13) + char(10);
PRINT N'COMMIT TRANSACTION'
PRINT N'GOTO EndSave'
PRINT N'QuitWithRollback:'
PRINT N'    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION'
PRINT N'EndSave:'
PRINT N'GO'
GO
/*******************************************
Create Schedules
********************************************/PRINT N'--Create Schedules'
PRINT char(13) + char(10)
PRINT N'DECLARE @ReturnCode INT';
PRINT N'SELECT @ReturnCode = 0';
DECLARE @schedule_name sysname
DECLARE @enabled int
DECLARE @freq_type int
DECLARE @freq_interval int
DECLARE @freq_subday_type int
DECLARE @freq_subday_interval int
DECLARE @freq_recurrence_factor int
DECLARE @active_start_date int
DECLARE @active_end_date int
DECLARE @active_start_time int
DECLARE @active_end_time int
DECLARE @owner_login_name sysname = N'sa' --update owner if required
Declare schedulecursor CURSOR FAST_FORWARD FOR
select [name] as schedule_name
,[enabled]
,freq_type
,freq_interval
,freq_subday_type
,freq_subday_interval
,freq_recurrence_factor
,active_start_date
,active_end_date
,active_start_time
,active_end_time
from msdb.dbo.sysschedules
OPEN schedulecursor
PRINT N'BEGIN TRANSACTION';
PRINT char(13) + char(10);
 FETCH NEXT FROM schedulecursor
 INTO @schedule_name
,@enabled
,@freq_type
,@freq_interval
,@freq_subday_type
,@freq_subday_interval
,@freq_recurrence_factor
,@active_start_date
,@active_end_date
,@active_start_time
,@active_end_time
 WHILE @@FETCH_STATUS = 0
 BEGIN
PRINT N'IF NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules WHERE name= ' + char(39) + @schedule_name  + char(39) + ')';
PRINT N'BEGIN'
PRINT N'EXEC @ReturnCode = sp_add_schedule';
PRINT N'@schedule_name = ' + '''' + cast(@schedule_name as nvarchar(max)) + '''' + ',';
PRINT N'@enabled = ' + cast(@enabled as nvarchar(max)) + ',';
PRINT N'@freq_type= ' + cast(@freq_type as nvarchar(max)) + ',';
PRINT N'@freq_interval= ' + cast(@freq_interval as nvarchar(max)) + ',';
PRINT N'@freq_subday_type= ' + cast(@freq_subday_type as nvarchar(max)) + ',';
PRINT N'@freq_subday_interval= ' + cast(@freq_subday_interval as nvarchar(max)) + ',';
PRINT N'@freq_recurrence_factor= ' + cast(@freq_recurrence_factor as nvarchar(max)) + ',';
PRINT N'@active_start_date= ' + cast(@active_start_date as nvarchar(max)) + ',';
PRINT N'@active_end_date= ' + cast(@active_end_date as nvarchar(max)) + ',';
PRINT N'@active_start_time= ' + cast(@active_start_time as nvarchar(max)) + ',';
PRINT N'@active_end_time= ' + cast(@active_end_time as nvarchar(max)) + ',';
PRINT N'@owner_login_name= ' + char(39) + cast(@owner_login_name as nvarchar(max)) + char(39);
PRINT char(13) + char(10);
PRINT N'IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback';
PRINT N'END'
PRINT char(13) + char(10);
FETCH NEXT FROM schedulecursor
 INTO @schedule_name
,@enabled
,@freq_type
,@freq_interval
,@freq_subday_type
,@freq_subday_interval
,@freq_recurrence_factor
,@active_start_date
,@active_end_date
,@active_start_time
,@active_end_time
END
PRINT char(13) + char(10);
PRINT N'COMMIT TRANSACTION'
PRINT N'GOTO EndSave'
PRINT N'QuitWithRollback:'
PRINT N'    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION'
PRINT N'EndSave:'
PRINT N'GO'
CLOSE schedulecursor;
DEALLOCATE schedulecursor;
GO
/*******************************************
Create Jobs
********************************************/PRINT N'--Create Jobs'
PRINT char(13) + char(10)
PRINT N'DECLARE @ReturnCode INT';
PRINT N'SELECT @ReturnCode = 0';
DECLARE @job_name sysname
DECLARE @enabled int
DECLARE @notify_level_eventlog int
DECLARE @notify_level_email int
DECLARE @notify_level_netsend int
DECLARE @notify_level_page int
DECLARE @delete_level int
DECLARE @description nvarchar(512)
DECLARE @category_name sysname
DECLARE @notify_email_operator_name sysname =N'DBAdmins'--set operator name here. Operator needs to already exist on target instance
DECLARE @owner_login_name sysname = N'sa'--update owner if required
Declare jobcursor CURSOR FAST_FORWARD FOR
SELECT sj.[name] jobname
      ,[enabled]
      ,[notify_level_eventlog]
      ,[notify_level_email]
      ,[notify_level_netsend]
      ,[notify_level_page]
      ,[delete_level]
  ,[description]
  ,sc.[name] categoryname
  FROM [dbo].[sysjobs] sj
  INNER JOIN [dbo].[syscategories] sc
  ON sj.category_id = sc.category_id
  OPEN jobcursor
PRINT N'BEGIN TRANSACTION';
PRINT char(13) + char(10);
 FETCH NEXT FROM jobcursor
 INTO @job_name 
 ,@enabled 
 ,@notify_level_eventlog 
 ,@notify_level_email 
 ,@notify_level_netsend 
 ,@notify_level_page 
 ,@delete_level 
 ,@description
 ,@category_name 
 WHILE @@FETCH_STATUS = 0
 BEGIN
PRINT N'IF NOT EXISTS (SELECT name FROM msdb.dbo.sysjobs WHERE name= ' + char(39) + @job_name  + char(39) + ')';
PRINT N'BEGIN'
PRINT N'EXEC @ReturnCode = sp_add_job';
PRINT N'@job_name = ' + '''' + cast(@job_name as nvarchar(max)) + '''' + ',';
PRINT N'@enabled = ' + cast(@enabled as nvarchar(max)) + ',';
IF @notify_level_eventlog > 0PRINT N'@notify_level_eventlog= ' + cast(@notify_level_eventlog as nvarchar(max)) + ',';
--Handle email notification unsupported in sp_add_job 
IF @notify_level_email > 0 AND @notify_email_operator_name IS NOT NULLPRINT N'@notify_level_email= ' + cast(@notify_level_email as nvarchar(max)) + ',';
IF @notify_level_email = 0 AND @notify_email_operator_name IS NOT NULLPRINT N'@notify_level_email= 1'  + ',';
IF @notify_level_netsend > 0PRINT N'@notify_level_netsend= ' + cast(@notify_level_netsend as nvarchar(max)) + ',';
IF @notify_level_page > 0PRINT N'@notify_level_page= ' + cast(@notify_level_page as nvarchar(max)) + ',';
IF @delete_level > 0PRINT N'@delete_level= ' + cast(@delete_level as nvarchar(max)) + ',';
IF @description IS NOT NULLPRINT N'@description= '+ char(39) + cast(@description as nvarchar(max))+ char(39) + ',';
IF @notify_email_operator_name IS NOT NULL PRINT N'@notify_email_operator_name= ' + char(39) + cast(@notify_email_operator_name as nvarchar(max)) + char(39) + ',';
PRINT N'@category_name= '+ char(39) + cast(@category_name as nvarchar(max))+ char(39) + ',';
PRINT N'@owner_login_name= ' + char(39) + cast(@owner_login_name as nvarchar(max)) + char(39);
PRINT char(13) + char(10);
PRINT N'IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback';
PRINT N'END'
PRINT char(13) + char(10);
 FETCH NEXT FROM jobcursor
 INTO @job_name 
 ,@enabled 
 ,@notify_level_eventlog 
 ,@notify_level_email 
 ,@notify_level_netsend 
 ,@notify_level_page 
 ,@delete_level 
 ,@description
 ,@category_name 

END
PRINT char(13) + char(10);
PRINT N'COMMIT TRANSACTION'
PRINT N'GOTO EndSave'
PRINT N'QuitWithRollback:'
PRINT N'    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION'
PRINT N'EndSave:'
PRINT N'GO'
CLOSE jobcursor;
DEALLOCATE jobcursor;
GO
/*******************************************
Create Job Steps
********************************************/PRINT N'--Create Jobs Steps'
PRINT char(13) + char(10)
PRINT N'DECLARE @ReturnCode INT';
PRINT N'SELECT @ReturnCode = 0';
DECLARE @job_name sysname
DECLARE @step_name sysname
DECLARE @step_id int
DECLARE@cmdexec_success_code int
DECLARE@on_success_action tinyint
DECLARE @on_success_step_id int
DECLARE@on_fail_action tinyint
DECLARE @on_fail_step_id int
DECLARE@retry_attempts int
DECLARE@retry_interval int
DECLARE@subsystem nvarchar(40)
DECLARE@command nvarchar(3200)
DECLARE@output_file_name nvarchar(200)
DECLARE @flags int
--DECLARE @server nvarchar(30)
--DECLARE @database_name sysname
--DECLARE @database_user_name sysname
Declare jobstepcursor CURSOR FAST_FORWARD FOR
SELECT sj.[name] jobname
      ,[step_name]
      ,[step_id]
  ,[cmdexec_success_code]
  ,[on_success_action]
      ,[on_success_step_id]
  ,[on_fail_action]
      ,[on_fail_step_id]
  ,[retry_attempts]
  ,[retry_interval]
  ,[subsystem]
  ,[command]
  ,[output_file_name]
      ,[flags]
      --,[server]
      --,[database_name]
      --,[database_user_name]
  FROM [dbo].[sysjobsteps] sjs
  INNER JOIN [dbo].[sysjobs] sj
  ON sjs.job_id = sj.job_id
  ORDER BY sjs.job_id, sjs.step_id
    OPEN jobstepcursor
PRINT N'BEGIN TRANSACTION';
PRINT char(13) + char(10);
 FETCH NEXT FROM jobstepcursor
 INTO @job_name, 
@step_name, 
@step_id, 
@cmdexec_success_code, 
@on_success_action, 
@on_success_step_id, 
@on_fail_action, 
@on_fail_step_id, 
@retry_attempts, 
@retry_interval, 
@subsystem, 
@command, 
@output_file_name, 
@flags --, 
--@server, 
--@database_name, 
--@database_user_name 
 WHILE @@FETCH_STATUS = 0
 BEGIN
PRINT N'IF NOT EXISTS (SELECT * FROM msdb.dbo.sysjobsteps sjs INNER JOIN msdb.dbo.sysjobs sj ON sjs.job_id = sj.job_id WHERE sj.[name] =' + char(39) + + cast(@job_name as nvarchar(max)) +  + char(39) + ' and step_id = ' +  cast(@step_id as nvarchar(max)) + ')';
PRINT N'BEGIN'
PRINT N'EXEC @ReturnCode = sp_add_jobstep';
PRINT N'@job_name = ' +  char(39) + cast(@job_name as nvarchar(max)) +  char(39) + ',';
PRINT N'@step_name = ' + char(39) + cast(@step_name as nvarchar(max)) + char(39) + ',';
PRINT N'@step_id= ' + cast(@step_id as nvarchar(max)) + ',';
PRINT N'@cmdexec_success_code= ' + cast(@cmdexec_success_code as nvarchar(max)) + ',';
PRINT N'@on_success_action= ' + cast(@on_success_action as nvarchar(max)) + ',';
PRINT N'@on_success_step_id= ' + cast(@on_success_step_id as nvarchar(max)) + ',';
PRINT N'@on_fail_action= ' + cast(@on_fail_action as nvarchar(max)) + ',';
PRINT N'@on_fail_step_id= ' + cast(@on_fail_step_id as nvarchar(max)) + ',';
PRINT N'@retry_attempts= ' + cast(@retry_attempts as nvarchar(max)) + ',';
PRINT N'@retry_interval= '  + cast(@retry_interval as nvarchar(max))+ ',';
PRINT N'@subsystem= ' + char(39) + cast(@subsystem as nvarchar(max)) + char(39)+ ',';
PRINT N'@command= ' + char(39) + replace( cast(@command as nvarchar(max)), char(39), char(39) + char(39)) + char(39)+ ',';
PRINT N'@output_file_name= ' + char(39) + cast(@output_file_name as nvarchar(max)) + char(39)+ ',';
PRINT N'@flags= '  + cast(@flags as nvarchar(max)) --+ ',';
--PRINT N'@server= ' + char(39) + cast(@server as nvarchar(max)) + char(39)+ ',';
--PRINT N'@database_name= ' + char(39) + cast(@database_name as nvarchar(max)) + char(39)+ ',';
--PRINT N'@database_user_name= ' + char(39) + cast(@database_user_name as nvarchar(max)) + char(39);
PRINT char(13) + char(10);
PRINT N'IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback';
PRINT N'END'
PRINT char(13) + char(10);
FETCH NEXT FROM jobstepcursor
 INTO @job_name, 
@step_name, 
@step_id, 
@cmdexec_success_code, 
@on_success_action, 
@on_success_step_id, 
@on_fail_action, 
@on_fail_step_id, 
@retry_attempts, 
@retry_interval, 
@subsystem, 
@command, 
@output_file_name, 
@flags --, 
--@server, 
--@database_name, 
--@database_user_name 

END
PRINT char(13) + char(10);
PRINT N'COMMIT TRANSACTION'
PRINT N'GOTO EndSave'
PRINT N'QuitWithRollback:'
PRINT N'    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION'
PRINT N'EndSave:'
PRINT N'GO'
CLOSE jobstepcursor;
DEALLOCATE jobstepcursor;
  GO
/*******************************************
Attach schedules
********************************************/PRINT N'--Attach schedules'
PRINT char(13) + char(10)
PRINT N'DECLARE @ReturnCode INT';
PRINT N'SELECT @ReturnCode = 0';
DECLARE @schedule_name sysname
DECLARE @job_name sysname
Declare scheduleattachcursor CURSOR FAST_FORWARD FOR
select ss.[name] schedule_name
,sj.name job_name 
from msdb.dbo.sysschedules ss
inner join msdb.dbo.sysjobschedules sjs
ON ss.schedule_id = sjs.schedule_id
inner join msdb.dbo.sysjobs sj
ON sjs.job_id = sj.job_id
OPEN scheduleattachcursor
PRINT N'BEGIN TRANSACTION';
PRINT char(13) + char(10);
 FETCH NEXT FROM scheduleattachcursor
 INTO @schedule_name
 ,@job_name
 WHILE @@FETCH_STATUS = 0
 BEGIN
PRINT N'BEGIN'
PRINT N'EXEC @ReturnCode = sp_attach_schedule';
PRINT N'@job_name = ' + '''' + cast(@job_name as nvarchar(max)) + '''' + ',';
PRINT N'@schedule_name = ' + '''' + cast(@schedule_name as nvarchar(max)) + '''' ;
PRINT N'IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback';
PRINT N'END'
PRINT char(13) + char(10);
FETCH NEXT FROM scheduleattachcursor
 INTO @schedule_name
 ,@job_name
 END
 PRINT char(13) + char(10);
PRINT N'COMMIT TRANSACTION'
PRINT N'GOTO EndSave'
PRINT N'QuitWithRollback:'
PRINT N'    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION'
PRINT N'EndSave:'
PRINT N'GO'
CLOSE scheduleattachcursor;
DEALLOCATE scheduleattachcursor;
GO
/*********************************************************
Set Job Server and Start Step
*********************************************************/PRINT N'--Set Job Server and Start Step'
PRINT char(13) + char(10)
PRINT N'DECLARE @ReturnCode INT';
PRINT N'SELECT @ReturnCode = 0';
DECLARE @job_name sysname
DECLARE @start_step_id int
Declare jobcursor CURSOR FAST_FORWARD FOR
SELECT [name] jobname
      ,start_step_id
  FROM [dbo].[sysjobs] 
  OPEN jobcursor
PRINT N'BEGIN TRANSACTION';
PRINT char(13) + char(10);
 FETCH NEXT FROM jobcursor
 INTO @job_name 
 ,@start_step_id 
 
 WHILE @@FETCH_STATUS = 0
 BEGIN
PRINT N'IF EXISTS (SELECT name FROM msdb.dbo.sysjobs WHERE name= ' + char(39) + @job_name  + char(39) + ')';
PRINT N'BEGIN'
PRINT N'EXEC @ReturnCode = sp_update_job';
PRINT N'@job_name = ' + '''' + cast(@job_name as nvarchar(max)) + '''' + ',';
PRINT N'@start_step_id = ' + cast(@start_step_id as nvarchar(max));
PRINT char(13) + char(10);
PRINT N'IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback';
PRINT N'END'
PRINT char(13) + char(10);
-- Add all jobs to local server (can be easily altered to support remote target servers if required)
PRINT N'IF NOT EXISTS (SELECT name FROM msdb.dbo.sysjobs sj INNER JOIN msdb.dbo.sysjobservers sjs ON sj.job_id = sjs.job_id  WHERE name= ' + char(39) + @job_name  + char(39) + ')';
PRINT N'BEGIN'
PRINT N'EXEC @ReturnCode = sp_add_jobserver';
PRINT N'@job_name = ' + '''' + cast(@job_name as nvarchar(max)) + '''' + ',';
PRINT N' @server_name = N' + char(39) + '(local)' + char(39);
PRINT char(13) + char(10);
PRINT N'IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback';
PRINT N'END'
PRINT char(13) + char(10);
 FETCH NEXT FROM jobcursor
 INTO @job_name 
 ,@start_step_id 

END
PRINT char(13) + char(10);
PRINT N'COMMIT TRANSACTION'
PRINT N'GOTO EndSave'
PRINT N'QuitWithRollback:'
PRINT N'    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION'
PRINT N'EndSave:'
PRINT N'GO'
CLOSE jobcursor;
DEALLOCATE jobcursor;
GO

Rate

You rated this post out of 5. Change rating

Share

Share

Rate

You rated this post out of 5. Change rating