modules/deploy/PublicHelpers/Database/Start-SqlServerAgentJob.ps1
<#
The MIT License (MIT) Copyright (c) 2015 Objectivity Bespoke Software Specialists Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #> function Start-SqlServerAgentJob { <# .SYNOPSIS Starts a SQL Server Agent job, synchronously or asynchronously. .PARAMETER JobName Name of the job to run. .PARAMETER ConnectionString Connection string that will be used to connect to the destination database. .PARAMETER StepName The name of the step at which to begin execution of the job. If empty, job will start at first step. .PARAMETER Synchronous If $true, job will be run synchronously (will wait until it ends). .PARAMETER SleepIntervalInSeconds Sleep interval when Synchronous is $true. .PARAMETER TimeoutInSeconds If specified and Synchronous is $true, function will fail after TimeoutInSeconds seconds. .PARAMETER ValidateRunOutcome If $true and Synchronous if $true, function will check job outcome and fail if it's not 'succeeded' or timeout occurs. .PARAMETER Credential Credential to use when opening connection to SQL Server (only if using Windows Authentication). .PARAMETER QueryTimeoutInSeconds Sql query timeout in seconds. .EXAMPLE Start-SqlServerAgentJob -JobName 'MyJob' -ConnectionString $Tokens.DatabaseConfig.DatabaseDeploymentConnectionString #> [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory=$true)] [string] $JobName, [Parameter(Mandatory=$true)] [string] $ConnectionString, [Parameter(Mandatory=$false)] [string] $StepName, [Parameter(Mandatory=$false)] [switch] $Synchronous = $true, [Parameter(Mandatory=$false)] [int] $SleepIntervalInSeconds = 5, [Parameter(Mandatory=$false)] [int] $TimeoutInSeconds, [Parameter(Mandatory=$false)] [switch] $ValidateRunOutcome = $true, [Parameter(Mandatory=$false)] [System.Management.Automation.PSCredential] $Credential, [Parameter(Mandatory=$false)] [int] $QueryTimeoutInSeconds ) if ($Synchronous) { $syncLog = 'synchronously' } else { $syncLog = 'asynchronously' } Write-ProgressExternal -Message "Running SQL Server Agent job $JobName" -ErrorMessage "SQL Server Agent job $JobName failed" Write-Log -Info "Running SQL Server Agent job named '$JobName' $syncLog using connectionString '$ConnectionString'" -Emphasize $sqlParams = @{ ConnectionString = $ConnectionString DatabaseName = '' SqlCommandMode = 'Scalar' Mode = '.net' } $jobId = Invoke-Sql @sqlParams -Query "select job_id from msdb.dbo.sysjobs where name = '$JobName'" if (!$jobId) { throw "Cannot find job named '$JobName' in msdb.dbo.sysjobs table." } $beforeRunMaxInstanceId = Invoke-Sql @sqlParams -Query "select max(isnull(instance_id, 0)) from msdb.dbo.sysjobhistory where job_id = '$jobId'" if (!$beforeRunMaxInstanceId -or $beforeRunMaxInstanceId -is [System.DBNull]) { $beforeRunMaxInstanceId = 0 } $sql = "DECLARE @output int; EXEC @output = msdb.dbo.sp_start_job @job_name=N'$JobName'" if ($StepName) { $sql += ", @step_name=N'$StepName'" } $sql += "; SELECT @output" $result = Invoke-Sql @sqlParams -Query $sql if ($result -ne 0) { throw "Failed to start job '$JobName' - sp_start_job failed with result code $result" } if (!$Synchronous) { Write-Log -Info "Job '$JobName' has been started successfully." Write-ProgressExternal -Message '' return } $runningSeconds = 0 do { $maxInstanceId = Invoke-Sql @sqlParams -Query "select max(isnull(instance_id, 0)) from msdb.dbo.sysjobhistory where job_id = '$jobId'" if ($maxInstanceId -isnot [System.DBNull] -and $maxInstanceId -gt $beforeRunMaxInstanceId) { break } Write-Log -Info "Job '$JobName' is still running (waiting part 1)." Start-Sleep -Seconds $SleepIntervalInSeconds $runningSeconds += $SleepIntervalInSeconds } while (!$TimeoutInSeconds -or $runningSeconds -lt $TimeoutInSeconds) do { $sessionId = Invoke-Sql @sqlParams -Query ` ("select top(1) session_id from msdb.dbo.sysjobactivity where job_id = '$jobId' and start_execution_date is not null and stop_execution_date is null " + ` "order by start_execution_date desc") if (!$sessionId -or $sessionId -is [System.DBNull]) { break } Write-Log -Info "Job '$JobName' is still running (waiting part 2)." Start-Sleep -Seconds $SleepIntervalInSeconds $runningSeconds += $SleepIntervalInSeconds } while (!$TimeoutInSeconds -or $runningSeconds -lt $TimeoutInSeconds) if (!$ValidateRunOutcome) { Write-ProgressExternal -Message '' Write-Log -Info "Job '$JobName' has finished. Run outcome has not been checked." return } if ($TimeoutInSeconds -and $runningSeconds -ge $TimeoutInSeconds) { Write-Log -Warn "Timeout occurred ($TimeoutInSeconds s)." } $sqlParams.SqlCommandMode = 'Dataset' $statusDataSet = (Invoke-Sql @sqlParams -Query "exec msdb.dbo.sp_help_job @job_name = '$JobName', @job_aspect = 'job'").Tables[0] $runDate = $statusDataSet.last_run_date $runTime = $statusDataSet.last_run_time $runOutcome = $statusDataSet.last_run_outcome $runOutcomeName = switch ($runOutcome) { 0 { 'failed'; break; } 1 { 'succeeded'; break; } 3 { 'canceled'; break; } default { 'unknown' } } if ($runOutcome -ne 1) { Write-Log -Info "History $jobId / $runDate / $runTime" $historyInfo = Invoke-Sql @sqlParams -Query ` ("select step_id, step_name, message from msdb.dbo.sysjobhistory where job_id = '$jobId' and " + ` "msdb.dbo.agent_datetime(run_date,run_time) >= msdb.dbo.agent_datetime($runDate, $runTime) order by step_id") $log = "Job '$JobName' has failed (outcome $runOutcome = '$runOutcomeName'). Run history:`r`n" foreach ($historyEntry in $historyInfo.Tables[0]) { $log += "Step $($historyEntry.step_id). '$($historyEntry.step_name)': $($historyEntry.message)`r`n" } Write-Log -Warn $log throw "Job '$JobName' has failed - see messages above for details." } Write-ProgressExternal -Message '' Write-Log -Info "Job '$JobName' has finished successfully." } |