Ready for final release
This commit is contained in:
271
backupSingle.ps1
Normal file
271
backupSingle.ps1
Normal file
@@ -0,0 +1,271 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$SqlInstance,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Directory,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
# backup.ps1
|
||||
#
|
||||
# TODO: Parallelize backups for multiple DBs in the instance
|
||||
|
||||
# Import SQL Server PowerShell module
|
||||
try {
|
||||
# Try to import the newer SqlServer module first
|
||||
if (Get-Module -ListAvailable -Name SqlServer) {
|
||||
Import-Module SqlServer -ErrorAction Stop
|
||||
Write-Host "INFO: SqlServer PowerShell module loaded successfully."
|
||||
}
|
||||
# Fall back to older SQLPS module if available
|
||||
elseif (Get-Module -ListAvailable -Name SQLPS) {
|
||||
Import-Module SQLPS -ErrorAction Stop
|
||||
Write-Host "INFO: SQLPS PowerShell module loaded successfully."
|
||||
}
|
||||
else {
|
||||
throw "No SQL Server PowerShell module found"
|
||||
}
|
||||
|
||||
# Verify Invoke-Sqlcmd is available
|
||||
if (-not (Get-Command Invoke-Sqlcmd -ErrorAction SilentlyContinue)) {
|
||||
throw "Invoke-Sqlcmd command not available"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "ERROR: Failed to import SQL Server PowerShell module. Please install it using: Install-Module -Name SqlServer -AllowClobber"
|
||||
Write-Host "ERROR: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$instanceName = $SqlInstance.Split('\')[1]
|
||||
|
||||
# Use provided directory parameter or default to instance-based path
|
||||
if ($Directory) {
|
||||
$directory = $Directory
|
||||
Write-Host "INFO: Using provided directory: $directory"
|
||||
} else {
|
||||
$directory = "C:\Rubrik\$instanceName"
|
||||
Write-Host "INFO: Using default directory: $directory"
|
||||
}
|
||||
|
||||
$fullBackupDay = 'Thursday'
|
||||
$fullBackupOverdueDays = 7 # Force full backup if last full backup is older than this many days
|
||||
$checkCluster = $false
|
||||
#$logFile = "C:\Rubrik\backup-$instanceName.log"
|
||||
$logFile = "H:\Backup\backup-$instanceName.log"
|
||||
|
||||
$fullFlag = $directory + "\last_full.flag"
|
||||
$diffFlag = $directory + "\last_diff.flag"
|
||||
$today = (Get-Date).Date
|
||||
|
||||
function FlagTakenToday($flagPath) {
|
||||
if (Test-Path $flagPath) {
|
||||
$flagDate = (Get-Content $flagPath | Out-String).Trim()
|
||||
return ($flagDate -eq $today.ToString("yyyy-MM-dd"))
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function GetLastFullBackupDate($flagPath) {
|
||||
if (Test-Path $flagPath) {
|
||||
$flagDate = (Get-Content $flagPath | Out-String).Trim()
|
||||
try {
|
||||
return [DateTime]::ParseExact($flagDate, "yyyy-MM-dd", $null)
|
||||
}
|
||||
catch {
|
||||
Write-Log "WARNING: Could not parse last full backup date from flag file: $flagDate"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function IsFullBackupOverdue($flagPath, $overdueDays) {
|
||||
$lastFullDate = GetLastFullBackupDate $flagPath
|
||||
if ($null -eq $lastFullDate) {
|
||||
Write-Log "WARNING: No last full backup date found. Full backup is considered overdue."
|
||||
return $true
|
||||
}
|
||||
|
||||
$daysSinceLastFull = ($today - $lastFullDate).Days
|
||||
$isOverdue = $daysSinceLastFull -gt $overdueDays
|
||||
|
||||
Write-Log "INFO: Last full backup was $daysSinceLastFull days ago on $($lastFullDate.ToString('yyyy-MM-dd')). Overdue threshold: $overdueDays days."
|
||||
|
||||
return $isOverdue
|
||||
}
|
||||
|
||||
function Write-Log($message) {
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
$logEntry = "$timestamp $message"
|
||||
Add-Content -Path $logFile -Value $logEntry
|
||||
Write-Host $logEntry
|
||||
}
|
||||
|
||||
# Check if directory exists and is a symbolic link (unless -Force is specified)
|
||||
if (-not (Test-Path $directory)) {
|
||||
Write-Log "ERROR: Directory '$directory' does not exist. Exiting script."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not $Force) {
|
||||
$directoryInfo = Get-Item $directory
|
||||
if (-not ($directoryInfo.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) {
|
||||
Write-Log "ERROR: Directory '$directory' is not a symbolic link. Exiting script."
|
||||
exit 1
|
||||
}
|
||||
Write-Log "INFO: Directory '$directory' exists and is a symbolic link. Target: $($directoryInfo.Target). Proceeding."
|
||||
} else {
|
||||
Write-Log "INFO: Force parameter specified. Skipping symbolic link check for directory '$directory'."
|
||||
}
|
||||
|
||||
if ($checkCluster) {
|
||||
# Check if SQL instance is running locally
|
||||
$localNode = $env:COMPUTERNAME
|
||||
|
||||
$clusterInstance = Get-ClusterResource | Where-Object { $_.ResourceType -eq "SQL Server" -and $_.Name -eq "SQL Server ($instanceName)" }
|
||||
if ($clusterInstance) {
|
||||
$ownerNode = $clusterInstance.OwnerNode.Name
|
||||
if ($ownerNode -ne $localNode) {
|
||||
Write-Log "SQL instance '$SqlInstance' is not running on local node '$localNode'. Exiting script."
|
||||
exit 1
|
||||
} else {
|
||||
Write-Log "SQL instance '$SqlInstance' is running on local node '$localNode'. Proceeding."
|
||||
}
|
||||
} else {
|
||||
Write-Log "ERROR: SQL instance '$SqlInstance' not found in cluster resources."
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
Write-Log "INFO: Cluster check is disabled. Proceeding without verification."
|
||||
}
|
||||
|
||||
# Check if full backup is overdue regardless of the day
|
||||
$isFullBackupOverdue = IsFullBackupOverdue $fullFlag $fullBackupOverdueDays
|
||||
|
||||
if ((Get-Date).DayOfWeek -eq $fullBackupDay) {
|
||||
if (-not (FlagTakenToday $fullFlag)) {
|
||||
$backupType = "FULL"
|
||||
$cleanupTime = 168
|
||||
Set-Content $fullFlag $today.ToString("yyyy-MM-dd")
|
||||
Write-Log "Selected FULL backup (scheduled day). Flag updated."
|
||||
} else {
|
||||
$backupType = "LOG"
|
||||
$cleanupTime = 24
|
||||
Write-Log "FULL backup already taken today. Selected LOG backup."
|
||||
}
|
||||
} elseif ($isFullBackupOverdue) {
|
||||
if (-not (FlagTakenToday $fullFlag)) {
|
||||
$backupType = "FULL"
|
||||
$cleanupTime = 168
|
||||
Set-Content $fullFlag $today.ToString("yyyy-MM-dd")
|
||||
Write-Log "Selected FULL backup (overdue - forcing full backup). Flag updated."
|
||||
} else {
|
||||
$backupType = "LOG"
|
||||
$cleanupTime = 24
|
||||
Write-Log "FULL backup already taken today (was overdue). Selected LOG backup."
|
||||
}
|
||||
} else {
|
||||
if (-not (FlagTakenToday $diffFlag)) {
|
||||
$backupType = "DIFF"
|
||||
$cleanupTime = 168
|
||||
Set-Content $diffFlag $today.ToString("yyyy-MM-dd")
|
||||
Write-Log "Selected DIFF backup. Flag updated."
|
||||
} else {
|
||||
$backupType = "LOG"
|
||||
$cleanupTime = 24
|
||||
Write-Log "DIFF backup already taken today. Selected LOG backup."
|
||||
}
|
||||
}
|
||||
|
||||
$query = "EXECUTE [dbo].[DatabaseBackup] @Databases = 'ALL_DATABASES', @Directory = '$directory', @BackupType = '$backupType', @Verify = 'N', @CleanupTime = $cleanupTime, @CheckSum = 'Y', @LogToTable = 'Y'"
|
||||
Write-Log "Executing backup type: $backupType"
|
||||
Write-Log "SQL Query: $query"
|
||||
|
||||
try {
|
||||
# Execute the backup using PowerShell SQL module with better error handling
|
||||
# Capture verbose output from Ola H scripts
|
||||
$infoMessages = @()
|
||||
|
||||
# Create event handlers to capture SQL Server messages
|
||||
$connection = New-Object System.Data.SqlClient.SqlConnection
|
||||
$connection.ConnectionString = "Server=$SqlInstance;Integrated Security=true;Connection Timeout=30"
|
||||
|
||||
# Event handler for informational messages (PRINT statements)
|
||||
$connection.add_InfoMessage({
|
||||
param($sqlSender, $e)
|
||||
$message = $e.Message
|
||||
if ($message -and $message.Trim() -ne "") {
|
||||
$script:infoMessages += $message
|
||||
Write-Log "SQL INFO: $message"
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
$connection.Open()
|
||||
|
||||
$command = New-Object System.Data.SqlClient.SqlCommand
|
||||
$command.Connection = $connection
|
||||
$command.CommandText = $query
|
||||
$command.CommandTimeout = 0 # No timeout for backup operations
|
||||
|
||||
Write-Log "Executing SQL command with message capture..."
|
||||
|
||||
# Execute and capture any result sets
|
||||
$reader = $command.ExecuteReader()
|
||||
|
||||
# Process any result sets
|
||||
while ($reader.Read()) {
|
||||
$rowData = @()
|
||||
for ($i = 0; $i -lt $reader.FieldCount; $i++) {
|
||||
$rowData += "$($reader.GetName($i)): $($reader.GetValue($i))"
|
||||
}
|
||||
if ($rowData.Count -gt 0) {
|
||||
Write-Log "SQL RESULT: $($rowData -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
$reader.Close()
|
||||
}
|
||||
finally {
|
||||
if ($connection.State -eq [System.Data.ConnectionState]::Open) {
|
||||
$connection.Close()
|
||||
}
|
||||
$connection.Dispose()
|
||||
}
|
||||
|
||||
Write-Log "$backupType Backup execution completed successfully."
|
||||
Write-Log "Total informational messages captured: $($infoMessages.Count)"
|
||||
}
|
||||
catch {
|
||||
Write-Log "ERROR: Backup execution failed with exception: $($_.Exception.Message)"
|
||||
|
||||
# Log additional SQL Server error details if available
|
||||
if ($_.Exception.InnerException) {
|
||||
Write-Log "ERROR: Inner Exception: $($_.Exception.InnerException.Message)"
|
||||
}
|
||||
|
||||
# Check for SQL Server specific errors
|
||||
if ($_.Exception -is [System.Data.SqlClient.SqlException]) {
|
||||
Write-Log "ERROR: SQL Server Error Details:"
|
||||
foreach ($sqlError in $_.Exception.Errors) {
|
||||
Write-Log "ERROR: Severity: $($sqlError.Class), State: $($sqlError.State), Number: $($sqlError.Number)"
|
||||
Write-Log "ERROR: Message: $($sqlError.Message)"
|
||||
if ($sqlError.Procedure) {
|
||||
Write-Log "ERROR: Procedure: $($sqlError.Procedure), Line: $($sqlError.LineNumber)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Clean up connection if it exists
|
||||
if ($connection -and $connection.State -eq [System.Data.ConnectionState]::Open) {
|
||||
$connection.Close()
|
||||
$connection.Dispose()
|
||||
}
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user