Los trabajos del Agente SQL Server no funcionan cuando los trabajos contienen pasos de trabajo que usan tokens después de instalar SQL Server 2005 Service Pack 1


Error #: 426808 (SQLBUDT)

Síntomas


Después de instalar Microsoft SQL Server 2005 Service Pack 1 (SP1), experimenta el siguiente comportamiento:
  • Los trabajos del Agente SQL Server no se realizan correctamente cuando los trabajos contienen pasos de trabajo que usan tokens.
  • Se recibe el siguiente mensaje de error:
    El paso de trabajo contiene uno o más tokens. Para SQL Server 2005 Service Pack 1 o versiones posteriores, todos los pasos de trabajo con tokens deben actualizarse con una macro antes de que se pueda ejecutar el trabajo.
Nota Este problema se produce con la compilación 2046 o versiones posteriores de SQL Server 2005.

Causa


En SQL Server 2005 SP1, la sintaxis de token de paso de trabajo del Agente SQL Server ha cambiado. Ahora, debe incluir una macro de escape con todos los tokens que se usan en los pasos del trabajo. Si no incluye una macro de escape, los pasos de trabajo no se realizarán correctamente. En la tabla siguiente se enumeran las macros de escape.
Macro de escapeDescripción
$(ESCAPE_SQUOTE(TokenName))Esta macro escapa a apóstrofos (') en la cadena de reemplazo de token. La macro reemplaza un apóstrofo por dos apóstrofos.
$(ESCAPE_DQUOTE(TokenName))Esta macro escapa a Comillas (") en la cadena de reemplazo de token. La macro reemplaza las comillas por dos comillas.
$(ESCAPE_RBRACKET(TokenName))Esta macro escapa a corchetes (]) en la cadena de reemplazo de token. La macro reemplaza un corchete de cierre por dos corchetes de cierre.
$ (ESCAPE_NONE (TokenName))La macro reemplaza un símbolo sin escapar ninguno de los caracteres de la cadena. Esta macro se proporciona para admitir compatibilidad con versiones anteriores en entornos donde solo se esperan cadenas de reemplazo de tokens de usuarios de confianza.
Por ejemplo, un paso de trabajo puede contener la siguiente instrucción Transact-SQL que usa el token a-DBN :
CREATE DATABASE [$(A-DBN)]
En este ejemplo, debe actualizar la sintaxis de token a la sintaxis siguiente:
CREATE DATABASE [$(ESCAPE_RBRACKET(A-DBN))]
Este cambio difiere del comportamiento previo de SQL Server 2005, en el que no se requirieron las macros de escape.

Resolución


Para resolver este problema, actualice todos los trabajos o solo los que usen tokens a la nueva sintaxis de token. Para ello, use el procedimiento almacenado sp_AddEscapeNoneToJobStepTokens . Puede crear este procedimiento almacenado mediante el siguiente script de Transact-SQL.Nota Asegúrese de que la compilación de SQL Server 2005 SP1 que instala es compilación 2046 o una compilación posterior. Además, debe ser miembro del rol fijo de servidor sysadmin para ejecutar el script.
      -- This script is used to automatically edit SQL Agent job steps so that-- unescaped tokens are prefaced with the ESCAPE_NONE syntax that was added in-- SQL Server 2005 SP1.if (@@microsoftversion < 0x90007FE)BEGIN    RAISERROR('This script should only be run on at least SQL Server 2005 SP1.', 20, 127) WITH LOG    returnENDuse msdbgoif exists (select * from sys.objects where name = N'fn_PrefaceTokensWithEscapeNone' and type = 'FN')    drop function fn_PrefaceTokensWithEscapeNonego-- This function manipulates @commands so that all bare tokens-- are prefaced with ESCAPE_NONE.create function fn_PrefaceTokensWithEscapeNone(@commands nvarchar(max)) RETURNS nvarchar(max)ASBEGIN    if (@commands IS NULL)    BEGIN        return @commands    END    -- In order to let this script run under SQLCMD mode, we define    -- the special "$(" variable start string by concatenation so that    -- sqlcmd mode does not think that we are defining one of its variables.    declare @strVariableStart nchar(2)    select @strVariableStart = N'$' + N'('    declare @idxTokenStart int    select @idxTokenStart = CHARINDEX(@strVariableStart, @commands)    while (@idxTokenStart != 0 and @idxTokenStart is not null)    BEGIN        declare @idxCloseParen int        select @idxCloseParen = CHARINDEX(N')', SUBSTRING(@commands, @idxTokenStart, LEN(@commands)))        -- Error checking. If there is no close parenthesis, return.        if (0 = @idxCloseParen)        BEGIN            return @commands        END        -- Deduce the token variable.        declare @tokenLen int        select @tokenLen = @idxCloseParen - LEN(@strVariableStart) - 1        declare @token nvarchar(max)        select @token = SUBSTRING(@commands, @idxTokenStart + LEN(@strVariableStart), @tokenLen)        -- Verify if @token contains a mis-matched number of open and        -- close parens. This behavior could happen if invalid syntax is        -- in a comment block. If so, skip to the next token.        declare @idx int        declare @cOpenParens int        declare @cCloseParens int        select @cOpenParens = 0        select @idx = CHARINDEX(N'(', @token);        while (@idx != 0)        BEGIN            select @cOpenParens = @cOpenParens + 1            select @idx = CHARINDEX(N'(', @token, @idx + 1);        END        select @cCloseParens = 0        select @idx = CHARINDEX(N')', @token);        while (@idx != 0)        BEGIN            select @cCloseParens = @cCloseParens + 1            select @idx = CHARINDEX(N')', @token, @idx + 1);        END        -- Special case for the WMI token.        if (N'WMI(' = SUBSTRING(@token, 1, LEN(N'WMI(')))        BEGIN                select @cOpenParens = @cOpenParens - 1        END        if ((@cOpenParens = @cCloseParens) and            (N'ESCAPE_NONE(' != SUBSTRING(@token, 1, LEN(N'ESCAPE_NONE('))) and            (N'ESCAPE_SQUOTE(' != SUBSTRING(@token, 1, LEN(N'ESCAPE_SQUOTE('))) and            (N'ESCAPE_DQUOTE(' != SUBSTRING(@token, 1, LEN(N'ESCAPE_DQUOTE('))) and            (N'ESCAPE_RBRACKET(' != SUBSTRING(@token, 1, LEN(N'ESCAPE_RBRACKET('))))        BEGIN            select @commands = STUFF(@commands, @idxTokenStart + LEN(@strVariableStart), @tokenLen, N'ESCAPE_NONE(' + @token + N')')        END        select @idxTokenStart = CHARINDEX(@strVariableStart, @commands, @idxTokenStart + 1)    END    return @commandsENDgoif exists (select * from sys.objects where name = N'sp_AddEscapeNoneToJobStepTokens' and type = 'P')    drop procedure sp_AddEscapeNoneToJobStepTokensgo-- This procedure allows you to update jobs so that bare tokens-- are prefaced with ESCAPE_NONE. By default, all jobs are updated.-- You can optionally specify @job_name, @job_id, or @owner_name-- to limit the jobs that will be affected.CREATE PROCEDURE sp_AddEscapeNoneToJobStepTokens(    @job_name nvarchar(128) = null,    @job_id uniqueidentifier = null,    @owner_name nvarchar(256) = null)AS  -- Find the jobs to update. These jobs must match all of the input  -- criteria, unless all of the inputs are null. In this case,   -- examine all jobs.  The jobs must also be jobs created locally,  -- such as sysjobs.originating_server_id = 0. These jobs should not be a job that we run  -- because another server told us to.  Furthermore, if the job  -- is local but it is meant to be run on a target server, we send an  -- update for the job.  declare @jobsToUpdate TABLE (job_id uniqueidentifier not null)    insert into @jobsToUpdate      select job_id      from sysjobs      where originating_server_id = 0 -- local jobs      and ((COALESCE(@job_name, sysjobs.name) = sysjobs.name) and           (COALESCE(@job_id, sysjobs.job_id) = sysjobs.job_id) and           (COALESCE(@owner_name, suser_sname(sysjobs.owner_sid)) = suser_sname(sysjobs.owner_sid)))    -- Now find the job steps to update, creating the new command by using  -- fn_PrefaceTokensWithEscapeNone.  declare @jobStepsToUpdate TABLE (job_id uniqueidentifier not null,                                    step_id int not null,                                    command_old nvarchar(max) null,                                    command_new nvarchar(max) null,                                   output_file_old nvarchar(max) null,                                    output_file_new nvarchar(max) null)    insert into @jobStepsToUpdate  (job_id, step_id, command_old, command_new, output_file_old, output_file_new)      select job_id, step_id, command, dbo.fn_PrefaceTokensWithEscapeNone(command), output_file_name, dbo.fn_PrefaceTokensWithEscapeNone(output_file_name)      from sysjobsteps      where sysjobsteps.job_id =       (select job_id        from @jobsToUpdate        where job_id = sysjobsteps.job_id)    -- Now we update the actual job step commands. We do this first before  -- we push out the updated jobs to the target servers so the  -- target servers actually get the updated version.  declare @updated_job_id uniqueidentifier  declare @updated_job_step_id int  declare @updated_job_step_command nvarchar(max)  declare @updated_job_step_output_file nvarchar(max)    declare job_steps_cursor CURSOR FOR      select job_id, step_id, command_new, output_file_new      from @jobStepsToUpdate      order by job_id, step_id    OPEN job_steps_cursor  FETCH NEXT from job_steps_cursor into @updated_job_id, @updated_job_step_id, @updated_job_step_command, @updated_job_step_output_file  WHILE (@@FETCH_STATUS <> -1)  BEGIN      IF (@@FETCH_STATUS <> -2)      BEGIN          EXEC sp_update_jobstep @job_id = @updated_job_id, @step_id = @updated_job_step_id, @command = @updated_job_step_command, @output_file_name = @updated_job_step_output_file      END      FETCH NEXT from job_steps_cursor into @updated_job_id, @updated_job_step_id, @updated_job_step_command, @updated_job_step_output_file  END    CLOSE job_steps_cursor  DEALLOCATE job_steps_cursor      -- For multiserver jobs, call the sp_post_msx_operation stored procedure to update  -- all the target servers. Note that the sp_post_msx_operation stored procedure is safe  -- to call because it verifies whether the job is really a multiserver job.  declare jobs_cursor CURSOR FOR      select job_id      from @jobsToUpdate    OPEN jobs_cursor  FETCH NEXT from jobs_cursor into @updated_job_id  WHILE (@@FETCH_STATUS <> -1)  BEGIN      IF (@@FETCH_STATUS <> -2)      BEGIN          EXEC sp_post_msx_operation @operation = 'UPDATE', @job_id = @updated_job_id      END      FETCH NEXT from jobs_cursor into @updated_job_id  END    CLOSE jobs_cursor  DEALLOCATE jobs_cursor  -- List the jobs that we ran on, including the previous command  -- text. We list all of the job steps, even the ones that we did not  -- update. Otherwise, a jumble of job steps from  -- different jobs run together and the output is not  -- useful.  select N'Warning - Jobs Updated' = N'The following job steps and job output file names were analyzed and potentially updated to add the ESCAPE_NONE macro before any job tokens that were not already escaped. Please review the modified job steps and replace ESCAPE_NONE with the correct escape macro.'    select suser_sname(jobs.owner_sid) as N'Job owner',          jobs.name as N'Job name',          jobs.job_id,          jobStepsUpdated.step_id,          N'Modified' = CASE WHEN jobStepsUpdated.command_new != jobStepsUpdated.command_old or jobStepsUpdated.output_file_new != jobStepsUpdated.output_file_old THEN 1 ELSE 0 END,         N'Command' = jobStepsUpdated.command_new,          N'Previous Command' = jobStepsUpdated.command_old,          N'Output file' = jobStepsUpdated.output_file_new,          N'Previous Output file' = jobStepsUpdated.output_file_old      from sysjobs as jobs, @jobsToUpdate as jobsUpdated, @jobStepsToUpdate as jobStepsUpdated      where jobsUpdated.job_id = jobs.job_id and jobsUpdated.job_id = jobStepsUpdated.job_id      order by 'Job name', jobStepsUpdated.step_idgo
Después de ejecutar el script, se crea el sp_AddEscapeNoneToJobStepTokens procedimiento almacenado. De forma predeterminada, actualizará todos los trabajos si ejecuta este procedimiento almacenado sin ningún parámetro. Si desea actualizar solo trabajos específicos, debe especificar valores no nulos para al menos uno de los tres parámetros siguientes:
  • @job_name
  • @job_id
  • @owner_name
Por ejemplo, puede usar la siguiente sintaxis:
  • Actualizar todos los trabajos:
    EXEC sp_AddEscapeNoneToJobStepTokens
  • Actualice un trabajo especificando el nombre del trabajo:
    EXEC sp_AddEscapeNoneToJobStepTokens 'MyJob'
  • Actualizar trabajos que pertenecen al mismo propietario:
    EXEC sp_AddEscapeNoneToJobStepTokens null,null,'JobOwner'
Este script agrega la ESCAPE_NONE macro a todos los pasos del trabajo que contienen tokens. Después de ejecutar este script, le recomendamos que revise los pasos de trabajo que usan tokens tan pronto como sea posible. A continuación, reemplace la macro ESCAPE_NONE por otra de las demás macros de escape apropiadas para el contexto del paso de trabajo.Nota: Si trabaja en un entorno de servidor maestro (MSX) y de servidor de destino (TSX), debe ejecutar este script en el servidor principal y en el servidor de destino para asegurarse de que los trabajos maestros del servidor de destino se actualicen correctamente. Para obtener más información sobre cómo actualizar trabajos para usar la nueva sintaxis y cómo usar las macros de escape para habilitar el reemplazo de tokens en pasos de trabajo del Agente SQL Server, consulte el tema "usar tokens en los pasos del trabajo" en SQL Server 2005 libros en pantalla (2006) o versiones posteriores de libros en pantalla de SQL Server 2005.

Estado


Microsoft ha confirmado que se trata de un problema de los productos de Microsoft recogidos en la sección "Se aplica a".

Referencias


Para obtener más información sobre cómo usar los tokens en los pasos de trabajo, visite el siguiente sitio web de Microsoft Developer Network (MSDN):