# encoding: ascii
# api: powershell
# title: Async SQL Backup 
# description: This will run multiple backup jobs at the same time. This script IS cluster aware.
# version: 0.1
# type: script
# author: Justin
# license: CC0
# x-poshcode-id: 2787
# x-archived: 2017-05-30T19:06:29
# x-published: 2012-07-13T12:11:00
#
# I will also post the Log Backup script (search for SQL Log Backup)
# This script can be run as either a SQL job or as a Task Scheduler job.
# Enjoy!
# Justin
# jrich523.wordpress.com
#
$backuppath = "\\server\sqlbackups\"
$alertaddress = "jrich523@domain.com"
$smtp = "smtp.domain.com"
$retaindays = 14
$hname = (gwmi win32_computersystem).name
$errorstate = 0
$body =@()
$backups = @()
$conns = @()
$completed = @{}

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
$dt = get-date -format "MMddyy"

$Instances = Get-Item "HKLM:\software\microsoft\microsoft sql server\instance names\sql"

foreach($InstanceName in $Instances.property)
{
	$InstancePath = $Instances.GetValue($InstanceName)
	if(Test-Path ("HKLM:\software\microsoft\microsoft sql server\" + $InstancePath + "\cluster"))
	{ #is cluster
		$ServerName = (gp ("HKLM:\software\microsoft\microsoft sql server\" + $InstancePath + "\cluster")).ClusterName
	}
	else #not cluster
	{
		$ServerName = $hname
	}
	
	if($InstanceName -eq "MSSQLSERVER")
	{#default Instance
		$InstConn = $ServerName
	}
	else
	{#named instance
		$InstConn = $ServerName + "\" + $InstanceName
	}


	$sql = New-Object Microsoft.SqlServer.management.Smo.Server $InstConn
	$backuppath += $sql.name + "\"
	$backuppath += $sql | ?{$_.instancename -ne "" -and $_.instancename -ne "MSSQLSERVER"} | %{$_.instancename + "\"}
	$dbs = $sql.databases | ? {!$_.isSystemObject}
	#change to full recovery if set to simple.
	#$dbs |?{$_.recoverymodel -eq [microsoft.sqlserver.management.smo.recoverymodel]::simple} | %{$_.recoverymodel = [microsoft.sqlserver.management.smo.recoverymodel]::Full;$_.alter()}


	$dbs | %{$completed[$_.name] = 0} #completed status

	foreach ($db in $dbs)
	{
	    $path = $backuppath + $db.name + "\"
	    if(!(Test-Path $path)){mkdir $path | Out-Null}
	    $conn = New-Object Microsoft.SqlServer.management.Smo.Server $InstConn
		$conn.ConnectionContext.StatementTimeout = 0
	    $bk = new-object microsoft.sqlserver.management.smo.backup
	    $bk.BackupSetDescription = "fullbackup of $($db.name) on $(get-date)"
	    $bk.BackupSetName = "full"
	    $bk.database = $db.name
	    $bk.Devices.AddDevice("$backuppath$($db.name)\$($db.name)-$dt.bak",'File')
	    $backups += $bk
	    $index = $backups.length -1
	    Register-ObjectEvent -InputObject $backups[$index] -EventName "Complete" -SourceIdentifier $db.name  -MessageData "$($db.name)-$index" | Out-Null
	    Register-ObjectEvent -InputObject $backups[$index] -EventName "Information" -SourceIdentifier "info-$($db.name)"  -MessageData "$($db.name)-$index" | out-null
	    $conns += $conn
	    $backups[$index].SqlBackupAsync($conns[$index])
	}
}

#### due to issue with completed trigger, another timer is being run to monitor the states of each backup.
$timer = New-Object timers.Timer
$timer.interval = 300000 #10 min
$action = {
$global:backups | ?{$_.asyncstatus.executionstatus -ne "InProgress"}|?{$global:completed.($_.database) -eq 0}|%{$global:completed.($_.database) = 1;$global:body += "timer caught: $($_.database)";"timer kicked off: $($_.database)"} 
	New-Event -SourceIdentifier timer
}
Register-ObjectEvent -InputObject $timer -Action $action -SourceIdentifier timercheck -EventName elapsed
$timer.start()


##wait for complete
while(($completed.values | measure -sum).sum -lt $backups.length){
	wait-event | Tee-Object -variable theevent | Remove-Event
	
    if($theevent.sourceIdentifier -ne "timer")
	{
	$msg = $theevent.sourceeventargs.error.message
    $db,$index = $theevent.messagedata.split('-')
    $status = $theevent.sourceargs[0].asyncstatus.executionstatus    

	if($completed.$db -ne 1)
    {

    	switch ($status){
    		"Succeeded" {
    			$completed.$db = 1
                $body += "$db  successfully"
    			break;}
    		"Failed" {
    			$body += "$db FAILED: $msg"
    			$completed.$db, $errorstate = 1
    			break;}
    		"InProgress"{
    			break;}
    		"Inactive" {
    			break;}
    		}
        }
	}
}

if($errorstate -eq 1){$subject = "DBBK: Failure on $hname"} else {$subject = "DBBK: Success on $hname"}
Send-MailMessage -Subject $subject -BodyAsHtml ([string]::join("<br>",$body)) -From $alertaddress -To $alertaddress -SmtpServer $smtp

#cleanup
$backups | %{$_.devices[0].name} | Split-Path | gci -Recurse | ? {!$_.PSIsContainer -and $_.lastWriteTime -lt [dateTime]::today.addDays(-1 * $retaindays)} | ri -force
