I have a SQL 2005 instance that runs a job that uses a Powershell script to rename the current SQL TX Log backup file by appending "-PrevDay" to it, (subsequently deleting the backup already named "XXX-PrevDay.bak" if it exists), and then run a full backup of the DB and a TX Log backup, if the DB is not in Simple mode.
SQL Server Agent Job kicks off the Powershell script through CMD in each job step and the powershell script kicks off the sql backup using "Invoke-SQLCmd" cmdlet. This works great, unless the backup fails, because the SQL job still shows as "Successful". This is because the SQL job that kicks off the Powershell script through the CMD prompt, only cares if the Powershell script runs...not if the commands IN the script actually succeed or fail.
Is it possible, using error trapping in powershell (or any method really), to have the powershell script "fail" the cmd prompt action of running the script...so that the SQL Job reports a failure?
Does this even make sense? LOL
I would assume that if I was able to use SQL 2008, which allows for a SQL job step type of "Powershell Script" (instead of the step type having to be Operating System...that kicks off the PS script) this wouldn't be an issue...however...that's not an option.
Right now, the job step runs the powershell script, through CMD using Parameters for DBName, Path, and Servername and looks like this:
powershell.exe "C:\SQLBackupScriptsTest\SQLServerBackup.ps1" -DBName 'Angel_Food' -Path 'E:\SQLBackup1' -Server 'DEVSQLSRV'
The actual Powershell script looks like this:
Param($DBName,$Path,$Server)
## Add sql snapins...must have for Invoke-Sqlcmd with powershell 2.0 ##
add-pssnapin sqlserverprovidersnapin100
add-pssnapin sqlservercmdletsnapin100
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null
## Set parameter for finding DB recovery model ##
$Recovery = (Invoke-Sqlcmd -Query "SELECT recovery_model_desc FROM sys.databases WHERE name = '$DBName'" -Server $Server)
## Do full backup of DB ##
(Invoke-Sqlcmd -Query "BACKUP DATABASE $DBName TO DISK = N'$Path\$DBName\$DBName.bak' WITH NOFORMAT, INIT, NAME = N'$DBNameTEST', SKIP, NOREWIND, NOUNLOAD, STATS = 10, CHECKSUM" -Server $Server -ConnectionTimeout 0 -QueryTimeout 65535)
############################################################################################################
## Check recovery mode, if FULL, check for Log-PrevDay.bak. If exists then delete. If not exist, move on ##
## Then check for Current TX log backup. If exists, rename to Log-PreDay.bak. If not exist, move on ##
## Then perform TX Log backup ##
## If recovery mode NOT FULL, do nothing ##
############################################################################################################
IF
($Recovery.recovery_model_desc -eq 'FULL')
#THEN#
{
## Look to see if PrevDay TX log exists. If so, delete, if not, move on ##
IF
(Test-Path $Path\$DBName\$DBName-Log-PrevDay.bak)
#THEN#
{remove-item $Path\$DBName\$DBName-Log-PrevDay.bak -force}
ELSE
{}
## Look to see if current TX log exists, if so, rename to Prev Day TX Log, if not, move on ##
IF
(Test-Path $Path\$DBName\$DBName-Log.bak)
#THEN#
{rename-item $Path\$DBName\$DBName-Log.bak -newname $DBName-Log-PrevDay.bak -force}
ELSE
{}
Invoke-Sqlcmd -Query "BACKUP LOG $DBName TO DISK = N'$Path\$DBName\$DBName-Log.bak' WITH NOFORMAT, INIT, NAME = N'$DBName LogTEST (Init)', SKIP, NOREWIND, NOUNLOAD, STATS = 10, CHECKSUM" -Server $Server -ConnectionTimeout 0 -QueryTimeout 65535}
ELSE
{}
Ok, after looking at a few blogs and a little trial/error/luck...I got it to do what I want.
I decided I needed to send the Powershell exit code back to the CMDEXEC. However, from what I found, Powershell always defaults an exit code of 0 (success)...unless you jump through a few hoops invloving using 2 PS scripts...which I really didn't want to do. So I decided to just trap any error and if any error was trapped, have it exit the PS script with a code of 1, no matter what. Honestly...all I really wanted was a reliable exit code of 0 (success) or 1 (fail). So ...long story short...here's how I changed my code.
I changed the CMDEXEC of each step to this:
powershell.exe -noprofile C:\SQLBackupScriptsTest\SQLServerBackup2.ps1 -DBName 'Angel_Food' -Path 'E:\SQLBackup' -Server 'DEVSQLSRV'
@Echo %errorlevel%
Then I changed my PS script to:
Param($DBName,$Path,$Server)
## Add sql snapins...must have for Invoke-Sqlcmd with powershell 2.0 ##
add-pssnapin sqlserverprovidersnapin100
add-pssnapin sqlservercmdletsnapin100
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null
## Set parameter for finding DB recovery model ##
$Recovery = (Invoke-Sqlcmd -Query "SELECT recovery_model_desc FROM sys.databases WHERE name = '$DBName'" -Server $Server)
## Do full backup of DB ##
trap {$_.Exception.Message; exit 1; continue}Invoke-Sqlcmd -Query "BACKUP DATABASE $DBName TO DISK = N'$Path\$DBName\$DBName.bak' WITH NOFORMAT, INIT, NAME = N'$DBNameTEST', SKIP, NOREWIND, NOUNLOAD, STATS = 10, CHECKSUM" -Server $Server -ConnectionTimeout 0 -QueryTimeout 65535 -ea stop
############################################################################################################
## Check recovery mode, if FULL, check for Log-PrevDay.bak. If exists then delete. If not exist, move on ##
## Then check for Current TX log backup. If exists, rename to Log-PreDay.bak. If not exist, move on ##
## Then perform TX Log backup ##
## If recovery mode NOT FULL, do nothing ##
############################################################################################################
IF
($Recovery.recovery_model_desc -eq 'FULL')
#THEN#
{
## Look to see if PrevDay TX log exists. If so, delete, if not, move on ##
IF
(Test-Path $Path\$DBName\$DBName-Log-PrevDay.bak)
#THEN#
{remove-item $Path\$DBName\$DBName-Log-PrevDay.bak -force}
ELSE
{}
## Look to see if current TX log exists, if so, rename to Prev Day TX Log, if not, move on ##
IF
(Test-Path $Path\$DBName\$DBName-Log.bak)
#THEN#
{rename-item $Path\$DBName\$DBName-Log.bak -newname $DBName-Log-PrevDay.bak -force}
ELSE
{}
trap {$_.Exception.Message; exit 1; continue}Invoke-Sqlcmd -Query "BACKUP LOG $DBName TO DISK = N'$Path\$DBName\$DBName-Log.bak' WITH NOFORMAT, INIT, NAME = N'$DBName LogTEST (Init)', SKIP, NOREWIND, NOUNLOAD, STATS = 10, CHECKSUM" -Server $Server -ConnectionTimeout 0 -QueryTimeout 65535 -ea stop}
ELSE
{}
Basically I added trap {$_.Exception.Message; exit 1; continue}
right in front of each Invoke-Sqlcmd
statement and ended each Invoke-Sqlcmd
statement with -ea stop
.
The trap $_.Exception.Message
traps any error... collects the error message, then exit 1
immediate exits the PS script with an exit code of 1
.
The SQL job reads each step with either a 0 or 1 and automatically interprets 0 as success and 1 as failure and marks the SQL Job as a success or failure correctly. Plus, since I captured the actual error message...it shows up in the SQL job history.
This wound up being exactly what I need. :)
If you're curious...here's the blogs that helped me the most:
来源:https://stackoverflow.com/questions/3823661/show-failure-for-sql-2005-job-that-executes-powershell-script-through-cmd-prompt