Zadania programu SQL Server Agent kończą się niepowodzeniem, gdy zadania zawierają instrukcje dotyczące pracy, które używają tokenów po zainstalowaniu dodatku Service Pack 1 dla programu SQL Server 2005


Usterka nr: 426808 (SQLBUDT)

Symptomy


Po zainstalowaniu dodatku Service Pack 1 (SP1) dla programu Microsoft SQL Server 2005 występują następujące działania:
  • Zadania programu SQL Server Agent kończą się niepowodzeniem, gdy zadania zawierają czynności, które używają tokenów.
  • Pojawi się następujący komunikat o błędzie:
    Krok zadania zawiera co najmniej jeden token. W przypadku programu SQL Server 2005 z dodatkiem Service Pack 1 lub nowszej wersji przed uruchomieniem zadania należy zaktualizować wszystkie kroki zadania z tokenem za pomocą makra.
Uwaga Ten problem występuje w kompilacji 2046 lub nowszej wersji programu SQL Server 2005.

Przyczyna


W dodatku SP1 dla programu SQL Server 2005 zmieniono składnię tokenu krok kroku zadania agenta programu SQL Server. Teraz należy dołączyć makro Escape ze wszystkimi tokenami, które są używane w krokach zadania. Jeśli nie zostanie uwzględnione makro Escape, te kroki zadania zakończą się niepowodzeniem. W poniższej tabeli wymieniono makra Escape.
Makro EscapeOpis
$(ESCAPE_SQUOTE(TokenName))To makro anuluje apostrofy (') w ciągu zastępującym token. Makro zamienia znak apostrof na dwa apostrofy.
$(ESCAPE_DQUOTE(TokenName))To makro anuluje znaki cudzysłowu (") w ciągu zastępującym token. Makro zastępuje znak cudzysłowu o podwójny cudzysłów.
$(ESCAPE_RBRACKET(TokenName))To makro anuluje nawiasy po prawej stronie (]) w ciągu zastępującym token. Makro zastępuje po jednym nawiasie po prawej.
$ (ESCAPE_NONE (tokenname))Makro zastępuje token bez ucieczki żadnych znaków w ciągu. To makro zapewnia obsługę zgodności z poprzednimi wersjami w środowiskach, w których ciągi zastępujące tokeny są oczekiwane tylko u zaufanych użytkowników.
Na przykład krok zadania może zawierać następującą instrukcję Transact-SQL, która używa tokenu a-DBN :
CREATE DATABASE [$(A-DBN)]
W tym przykładzie należy zaktualizować składnię tokenu do następującej składni:
CREATE DATABASE [$(ESCAPE_RBRACKET(A-DBN))]
Ta zmiana różni się od wcześniejszego zachowania programu SQL Server 2005, gdzie nie są wymagane makra Escape.

Rozwiązanie


Aby rozwiązać ten problem, zaktualizuj wszystkie lub tylko konkretne zadania, które używają tokenów do nowej składni tokenu. Aby to zrobić, użyj sp_AddEscapeNoneToJobStepTokens procedury składowanej. Tę procedurę przechowywaną można utworzyć, korzystając z poniższego skryptu Transact-SQL.Uwaga Upewnij się, że instalowana wersja dodatku SP1 dla programu SQL Server 2005 to kompilacja 2046 lub nowsza kompilacja. Ponadto musisz być członkiem stałej roli serwera sysadmin w celu uruchomienia skryptu.
      -- 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
Po uruchomieniu skryptu zostanie utworzona sp_AddEscapeNoneToJobStepTokens procedura składowana. Domyślnie wszystkie zadania zostaną zaktualizowane, jeśli ta procedura składowana zostanie uruchomiona bez żadnych parametrów. Jeśli chcesz zaktualizować tylko określone zadania, musisz określić wartości inne niż null dla co najmniej jednego z następujących trzech parametrów:
  • @job_name
  • @job_id
  • @owner_name
Możesz na przykład użyć następującej składni:
  • Aktualizuj wszystkie zadania:
    EXEC sp_AddEscapeNoneToJobStepTokens
  • Zaktualizuj zadanie, określając nazwę zadania:
    EXEC sp_AddEscapeNoneToJobStepTokens 'MyJob'
  • Aktualizuj zadania należące do tego samego właściciela:
    EXEC sp_AddEscapeNoneToJobStepTokens null,null,'JobOwner'
Ten skrypt dodaje makro ESCAPE_NONE do wszystkich kroków zadania zawierających tokeny. Po uruchomieniu tego skryptu zalecamy zapoznanie się z instrukcjami zadania, które używają tokenów w możliwie najkrótszym czasie. Następnie zastąp makro ESCAPE_NONE przy użyciu jednego z innych makr Escape odpowiedniego dla kontekstu kroku zadania.Uwaga Jeśli pracujesz w środowisku serwera głównego (głównego) i serwera docelowego (TSX), musisz uruchomić ten skrypt zarówno na serwerze głównym, jak i w TSX, aby upewnić się, że zadania główne na TSX są poprawnie zaktualizowane. Aby uzyskać więcej informacji na temat aktualizowania zadań w celu korzystania z nowej składni i używania makr Escape w celu włączenia zamiany tokenów w zadaniu programu SQL Server Agent, zobacz temat "Korzystanie z tokenów w krokach zadania" w programie SQL Server 2005 Books Online (kwiecień 2006) lub nowszych wersjach programu SQL Server 2005 Books Online.

Stan


Firma Microsoft potwierdziła, że jest to problem występujący w produktach firmy Microsoft wymienionych w sekcji "dotyczy".

Informacje


Aby uzyskać więcej informacji na temat korzystania z tokenów w krokach, odwiedź następującą witrynę Microsoft Developer Network (MSDN) w sieci Web: