Dell AppAssure – Synchronous Backup through PowerShell

0

Almost a year ago I was asked to write a little script.
This customer had chosen Dell AppAssure as their backup solution.
Note that I’m not bashing the product since I think it’s a solid solution.
However… as always a sales guy sold the product to a customer telling them ‘Yes, we can!’ when instead he should have said ‘No, we can not!’.
In the first place I want to thank this sales guy because he has brought me some new customers… :-)
So, the customer was expecting AppAssure to be able to perform synchronous backups of i.e. a database- and file server.
But AppAssure is built for scheduled continuous backups!
You define that a server needs to get a backup ever X hours. When this time is passed, it will start a new backup.

So when the schedule is every 2 hours and the database server finished before that but the file server doesn’t, the database server will begin a new backup while the file server is still performing the previous backup task… and there we have the inconsistency.

So, this customer asked me to solve this.
I don’t think it needed to be solved since AppAssure simply has a different backup philosophy…
But I found it to be challenging nevertheless so here’s my solution (Dell AppAssure.ps1) :

param (
    $CSV
)
$CSVName = import-csv $CSV

function Write-AppAssureToEventLog {
    [CmdletBinding()]
    [OutputType([int])]
    param (
        [parameter(Mandatory=$true)][int]$EventID,
        [Parameter(Mandatory=$true)][string]$ErrorMessage,
        [parameter(Mandatory=$true)][validateset("Error","Information","Warning")][string]$Level
    ) 
    try {
        [System.Diagnostics.EventLog]::CreateEventSource("AppAssureScript","Application")
    } catch {}
    Write-EventLog -LogName 'Application' -Source 'AppAssureScript' -EventId $EventID -Message $ErrorMessage -EntryType $Level
}

function Check-JobStatus {
    param (
        [parameter(mandatory=$true,position=0)]$ProtectedServer
    )
    $Check = Get-Activejobs -ProtectedServer $ProtectedServer
    if (($Check -eq "") -or ($Check -eq $null)) {
        write-output $false
    } else {
        write-output $true
    }
}

workflow Start-AppAssureSynchronousBackup {
    param (
        [parameter(mandatory=$true)]$ComputerName
    )
    foreach -parallel ($Computer in $ComputerName) {
        $TargetSnapshotJob = New-Snapshot -ProtectedServer $Computer
        $TargetCurrentJob = $true
        while ($TargetCurrentJob -eq $true) {
            Start-Sleep -seconds 5
            $TargetCurrentJob = Check-JobStatus -ProtectedServer $Computer
        }
        Write-Output "* * * Backup job finished for $Computer * * *"
        $Status = Get-Completedjobs -ProtectedServer $Computer
        if ($Status[0].Status -eq 'Succeeded') {
            Write-AppAssureToEventLog -EventID '33182' -ErrorMessage ("AppAssure backup procedure completed for: $Computer")
        } else {
            Write-AppAssureToEventLog -EventID '33183' -ErrorMessage ("AppAssure backup procedure failed for: $Computer")
        }
    }
}

Import-Module AppAssurePowerShellModule

foreach ($Entry in $CSVName) { 
    $Targets  = @()
    if ($Entry.Target0 -ne "") { $Targets += $Entry.Target0 }
    if ($Entry.Target1 -ne "") { $Targets += $Entry.Target1 }
    if ($Entry.Target2 -ne "") { $Targets += $Entry.Target2 }
    if ($Entry.Target3 -ne "") { $Targets += $Entry.Target3 }
    if ($Entry.Target4 -ne "") { $Targets += $Entry.Target4 }
    $Target0 = Get-ProtectedServers | where {$_.DisplayName -eq "$Targets[0]"}
    $Target1 = Get-ProtectedServers | where {$_.DisplayName -eq "$Targets[1]"}
    $Target2 = Get-ProtectedServers | where {$_.DisplayName -eq "$Targets[2]"}
    $Target3 = Get-ProtectedServers | where {$_.DisplayName -eq "$Targets[3]"}
    $Target4 = Get-ProtectedServers | where {$_.DisplayName -eq "$Targets[4]"}

    if (($Target0 -ne "") -and ($Target1 -ne "") -and ($Target2 -ne "") -and ($Target3 -ne "") -and ($Target4 -ne "")) {
        if ($Target0 -ne "") { $Target0SnapshotJob = New-Snapshot -ProtectedServer $Targets[0] }
        if ($Target1 -ne "") { $Target1SnapshotJob = New-Snapshot -ProtectedServer $Targets[1] }
        if ($Target2 -ne "") { $Target2SnapshotJob = New-Snapshot -ProtectedServer $Targets[2] }
        if ($Target3 -ne "") { $Target3SnapshotJob = New-Snapshot -ProtectedServer $Targets[3] }
        if ($Target4 -ne "") { $Target4SnapshotJob = New-Snapshot -ProtectedServer $Targets[4] }
        Start-Sleep -Seconds 5

        # Check job status
        $Target0CurrentJob = $false
        $Target1CurrentJob = $false
        $Target2CurrentJob = $false
        $Target3CurrentJob = $false
        $Target4CurrentJob = $false
        if ($Target0 -ne "") { $Target0CurrentJob = Check-JobStatus -ProtectedServer $Targets[0] }
        if ($Target1 -ne "") { $Target1CurrentJob = Check-JobStatus -ProtectedServer $Targets[1] }
        if ($Target2 -ne "") { $Target2CurrentJob = Check-JobStatus -ProtectedServer $Targets[2] }
        if ($Target3 -ne "") { $Target3CurrentJob = Check-JobStatus -ProtectedServer $Targets[3] }
        if ($Target4 -ne "") { $Target4CurrentJob = Check-JobStatus -ProtectedServer $Targets[4] }

        # Check job status - loop
        while (($Target0CurrentJob -eq $true) -or ($Target1CurrentJob -eq $true) -or ($Target2CurrentJob -eq $true) -or ($Target3CurrentJob -eq $true) -or ($Target4CurrentJob -eq $true)) {
            Start-Sleep -Seconds 3
            if ($Target0 -ne "") { $Target0CurrentJob = Check-JobStatus -ProtectedServer $Targets[0] }
            if ($Target1 -ne "") { $Target1CurrentJob = Check-JobStatus -ProtectedServer $Targets[1] }
            if ($Target2 -ne "") { $Target2CurrentJob = Check-JobStatus -ProtectedServer $Targets[2] }
            if ($Target3 -ne "") { $Target3CurrentJob = Check-JobStatus -ProtectedServer $Targets[3] }
            if ($Target4 -ne "") { $Target4CurrentJob = Check-JobStatus -ProtectedServer $Targets[4] }
        }
        # Output
        $Array = @()
        if ($Target0 -ne "") { 
            Write-Output "* * * Backup job finished for $($Targets[0]) * * *"
            $Array += "$Target0 "
            $Status = Get-Completedjobs -ProtectedServer $Targets[0]
            if ($Status[0].Status -eq 'Succeeded') {
                Write-AppAssureToEventLog -EventID '33182' -ErrorMessage ("AppAssure backup procedure completed for: "+$Targets[0]) -Level 'Information'
            } else {
                Write-AppAssureToEventLog -EventID '33183' -ErrorMessage ("AppAssure backup procedure failed for: "+$Targets[0]) -Level 'Error'
            }
        }
        if ($Target1 -ne "") {
            Write-Output "* * * Backup job finished for $($Targets[1]) * * *"
            $Array += "$Target1 "
            $Status = Get-Completedjobs -ProtectedServer $Targets[1]
            if ($Status[1].Status -eq 'Succeeded') {
                Write-AppAssureToEventLog -EventID '33182' -ErrorMessage ("AppAssure backup procedure completed for: "+$Targets[1]) -Level 'Information'
            } else {
                Write-AppAssureToEventLog -EventID '33183' -ErrorMessage ("AppAssure backup procedure failed for: "+$Targets[1]) -Level 'Error'
            }
        }
        if ($Target2 -ne "") {
            Write-Output "* * * Backup job finished for $($Targets[2]) * * *"
            $Array += "$Target2 "
            $Status = Get-Completedjobs -ProtectedServer $Targets[2]
            if ($Status[2].Status -eq 'Succeeded') {
                Write-AppAssureToEventLog -EventID '33182' -ErrorMessage ("AppAssure backup procedure completed for: "+$Targets[2]) -Level 'Information'
            } else {
                Write-AppAssureToEventLog -EventID '33183' -ErrorMessage ("AppAssure backup procedure failed for: "+$Targets[2]) -Level 'Error'
            }
        }
        if ($Target3 -ne "") {
            Write-Output "* * * Backup job finished for $($Targets[3]) * * *"
            $Array += "$Target3 "
            $Status = Get-Completedjobs -ProtectedServer $Targets[3]
            if ($Status[3].Status -eq 'Succeeded') {
                Write-AppAssureToEventLog -EventID '33182' -ErrorMessage ("AppAssure backup procedure completed for: "+$Targets[3]) -Level 'Information'
            } else {
                Write-AppAssureToEventLog -EventID '33183' -ErrorMessage ("AppAssure backup procedure failed for: "+$Targets[3]) -Level 'Error'
            }
        }
        if ($Target4 -ne "") {
            Write-Output "* * * Backup job finished for $($Targets[4]) * * *"
            $Array += "$Target4 "
            $Status = Get-Completedjobs -ProtectedServer $Targets[4]
            if ($Status[4].Status -eq 'Succeeded') {
                Write-AppAssureToEventLog -EventID '33182' -ErrorMessage ("AppAssure backup procedure completed for: "+$Targets[4]) -Level 'Information'
            } else {
                Write-AppAssureToEventLog -EventID '33183' -ErrorMessage ("AppAssure backup procedure failed for: "+$Targets[4]) -Level 'Error'
            }
        }
    }
}

So how does it work?

Files
The files for the scripted AppAssure synchronous backup are located on the AppAssure management server at ‘C:\AppAssure’.
1

The script
The script is located in ‘C:\AppAssure’ on the AppAssure server and is named ‘AppAssure_SynchronousBackup.ps1’. This script takes a CSV name as a parameter when executed through the Windows Task Scheduler.

The content (CSV)
For each group a backup schedule is decided. For each backup schedule there is a separate CSV file named ‘Group.csv’. These CSV files have headers named ‘Target’
Each row in such a file is considered a group of server that need their backup to be in sync. There is a maximum of 5 servers per group:
2

Task Scheduler
The Windows task scheduler is used to execute the backup jobs according to a defined schedule.
This schedule is configured in the task, not in the script.
3

Event log
The script writes output to the Application event log in Windows.
4

The source for the events is ‘AppAssureScript’ where the EventID ‘33182’ means the backup job has succeeded and Event ID ‘33183’ means the backup job has failed.
The name of the server is shown in the message of the event:
5

Howto add a new group of server to an existing backup job
Edit the appropriate CSV file and add a new group of servers.
6

Howto create a new backup job
1) Create a new CSV file the the headers as shown in the following example:
7
2) Create a new Scheduled Task with the following content:
8
The arguments are ‘-ExecutionPolicy bypass -command C:\AppAssure\AppAssure_SynchronousBackup.ps1 -CSV C:\AppAssure\group1.csv ‘ without the quotes and with the appropriate CSV file.
3) Configure the desired schedule for the backup task to run by defining a trigger:
9

The finishing touch
1) Define and configure the backup schedules (Window Task Scheduler)
2) Enter the desired groups of servers in the appropriate CSV files.
3) Configure the internal monitoring tool to monitor failed backup jobs by using the events written to the event log by the script.

PowerShell function to remove empty lines

0

For some time no I have to remove empty (white) lines from a text file or an array.
Here’s the function I wrote that I would like to share with you… I hope you’ll find it useful :-)

You can use it like so:

$array = ‘a’,’b’,”,’c’,’d’,”,”,’e’
$Array | Remove-EmptyLines

Remove-EmptyLines -InputObject $array

Remove-EmptyLines -InputObject (Get-Content myfile.txt)

Get-Content myfile.txt | Remove-EmptyLines

function Remove-EmptyLines {
<#
    .SYNOPSIS
    Remove the empty lines from an array.
    .DESCRIPTION
    A PowerShell function to remove the empty lines from an array.
    .EXAMPLE
    $array = 'a','b','','c','d','','','e'
    $Array | Remove-EmptyLines
    .EXAMPLE
    Remove-EmptyLines -InputObject (Get-Content Myfile.txt)
    .NOTES
    Author : Jeff Wouters
    Date   : 16th of April 2014
#>
    [cmdletbinding()]
    param (
        [parameter(mandatory=$true,position=0,ValueFromPipeline=$true)][array]$InputObject
    )
    begin {
    } process {
        $InputObject.split('',[System.StringSplitOptions]::RemoveEmptyEntries)
    } end {
    }
}

A first glimpse at my Active Directory Health Check

0

Last week I presented at the Virtual PowerShell User Group about the big secret PowerShell project I’ve been working on (codename: possessod).
As I wrote earlier: It’s an Active Directory Health Check.

The host of the Virtual PowerShell User Group, Joel Bennett, was kind enough to post the recording online for you all to view.
I hope you enjoy the video. :-)


For those interested: I’ll be releasing v1 of the script at the E2E Virtualization Conference in Brussels (sold out!)….

Go to Top