diff --git a/backupmult.ps1 b/backupmult.ps1 index c18a041..e7ec63e 100644 --- a/backupmult.ps1 +++ b/backupmult.ps1 @@ -1,72 +1,29 @@ param( [Parameter(Mandatory=$true)] [string]$SqlInstance, + + [Parameter(Mandatory=$true)] + [string]$MvName, [Parameter(Mandatory=$false)] - [string]$Directories, - - [Parameter(Mandatory=$false)] - [int]$Jobs = 2, - - [Parameter(Mandatory=$false)] - [switch]$Force + [int]$Jobs = 2 ) # -# backupmult.ps1 - Parallel database backup script using Ola Hallengren's DatabasesInParallel feature +# backupmult.ps1 - Parallel database backup script using Ola H # # Uses Ola H's built-in parallel processing by starting multiple concurrent backup jobs # Each job will automatically share the database load using DatabasesInParallel=Y -# -# Import SQL Server PowerShell module -try { - if (Get-Module -ListAvailable -Name SqlServer) { - Import-Module SqlServer -ErrorAction Stop - Write-Host "INFO: SqlServer PowerShell module loaded successfully." - } - 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" - } - - 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 directories or default to comma-separated multi-directory setup -if ($Directories) { - $directoryParam = $Directories - Write-Host "INFO: Using provided directories: $directoryParam" -} else { - $directoryParam = "C:\Rubrik\$instanceName\Dir1, C:\Rubrik\$instanceName\Dir2, C:\Rubrik\$instanceName\Dir3, C:\Rubrik\$instanceName\Dir4" - Write-Host "INFO: Using default multi-directory setup: $directoryParam" -} +# TODO: Log file management (don't just overwrite existing logs) +# TODO: Dynmically figure out MV channels and paths using Rubrik API +# TODO: See if there is way to query QueueDatabase during backup to monitor progress $fullBackupDay = 'Thursday' $fullBackupOverdueDays = 7 +$instanceName = $SqlInstance.Split('\')[1] $logFile = "C:\Rubrik\backup-multi-$instanceName.log" - -# Validate job count -if ($Jobs -lt 1 -or $Jobs -gt 8) { - Write-Host "ERROR: Jobs parameter must be between 1 and 8. Provided: $Jobs" - exit 1 -} - -Write-Host "INFO: Starting $Jobs parallel backup jobs" - -$today = (Get-Date).Date +$SAFile = "C:\Rubrik\scripts\rbksql.xml" function Write-Log($message, $jobId = "") { $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" @@ -94,6 +51,131 @@ function Write-Log($message, $jobId = "") { Write-Host $logEntry } +# Import SQL Server PowerShell module +try { + if (Get-Module -ListAvailable -Name SqlServer) { + Import-Module SqlServer -ErrorAction Stop + Write-Log "INFO: SqlServer PowerShell module loaded successfully." + } + elseif (Get-Module -ListAvailable -Name SQLPS) { + Import-Module SQLPS -ErrorAction Stop + Write-Log "INFO: SQLPS PowerShell module loaded successfully." + } + else { + throw "No SQL Server PowerShell module found" + } + + 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 +} + +# Import Rubrik Security Cloud module +try { + Import-Module RubrikSecurityCloud -ErrorAction Stop + Write-Log "INFO: RubrikSecurityCloud module loaded successfully." +} catch { + Write-Log "ERROR: Failed to import RubrikSecurityCloud module. $($_.Exception.Message)" + exit 1 +} + +$localNode = $env:COMPUTERNAME +$clusterInstance = Get-ClusterResource | Where-Object { $_.ResourceType -eq "SQL Server" -and $_.Name -eq "SQL Server ($instanceName)" } + +if ($clusterInstance) { + $ownerNode = $clusterInstance.OwnerNode + if ($ownerNode -ne $localNode) { + Write-Log "SQL instance '$SqlInstance' is not running on local node '$localNode'. Updating the MV." + + Connect-Rsc -ServiceAccountFile $SAFile + Write-Log "Connected to Rubrik Security Cloud." + + $newHost = Get-RscHost -Name $ownerNode -OsType WINDOWS + + $query = New-RscQuery -GqlQuery slaManagedVolumes -AddField Nodes.HostDetail, Nodes.SmbShare, Nodes.ClientConfig, Nodes.ClientConfig.BackupScript, Nodes.ClientConfig.PreBackupScript + $query.var.filter = @(Get-RscType -Name Filter) + $query.var.filter[0].field = "NAME_EXACT_MATCH" + $query.var.filter[0].Texts = $mvName + $mvDetail = $query.Invoke().nodes[0] + + Write-Log "Found Managed Volume: $($mvDetail.Name) (ID: $($mvDetail.Id), Status: $($mvDetail.hostDetail.Status), HostDetail Name: $($mvDetail.hostDetail.Name))" + + $query = New-RscMutation -GqlMutation updateManagedVolume + $query.Var.input = Get-RscType -Name UpdateManagedVolumeInput + $query.Var.input.update = Get-RscType -Name ManagedVolumeUpdateInput + $query.Var.input.update.config = Get-RscType -Name ManagedVolumePatchConfigInput + $query.Var.input.update.slaClientConfig = Get-RscType -Name ManagedVolumePatchSlaClientConfigInput + + $query.Var.input.Id = $mvDetail.Id + $query.Var.input.update.Name = $mvName + $query.Var.input.update.config.SmbDomainName = $mvDetail.SmbShare.DomainName + $query.Var.input.update.config.SmbValidIps = $newHost.Name + $query.Var.input.update.config.SmbValidUsers = $mvDetail.SmbShare.ValidUsers + $mvDetail.SmbShare.ActiveDirectoryGroups + $query.Var.input.update.slaClientConfig.clientHostId = $newHost.Id + $query.Var.input.update.slaClientConfig.channelHostMountPaths = $mvDetail.ClientConfig.ChannelHostMountPaths + $query.Var.input.update.slaClientConfig.backupScriptCommand = $mvDetail.ClientConfig.BackupScript.ScriptCommand + $query.Var.input.update.slaClientConfig.preBackupScriptCommand = $mvDetail.ClientConfig.PreBackupScript.ScriptCommand + $query.Var.input.update.slaClientConfig.preBackupScriptTimeout = $mvDetail.ClientConfig.PreBackupScript.Timeout + $query.Var.input.update.slaClientConfig.shouldDisablePostBackupScriptOnBackupFailure = $true + $query.Var.input.update.slaClientConfig.shouldDisablePostBackupScriptOnBackupSuccess = $true + $query.Var.input.update.slaClientConfig.shouldDisablePreBackupScript = $false + $query.Var.input.update.slaClientConfig.shouldCancelBackupOnPreBackupScriptFailure = $mvDetail.ClientConfig.ShouldCancelBackupOnPreBackupScriptFailure + + $query.gqlRequest().Variables + + if (-not $dryrun) { + $result = $query.Invoke() + } else { + Write-Log "Dry run mode: Managed Volume update not invoked." + } + + # Now must exit 1 to stop the backup continuing on the wrong node + Disconnect-Rsc + exit 1 + + } else { + Write-Log "SQL instance '$SqlInstance' is running on local node '$localNode'. No action needed." + } +} else { + Write-Log "ERROR: SQL instance '$SqlInstance' not found in cluster resources." + exit 1 +} + +# Connect to Rubrik and retrieve managed volume paths +try { + Connect-Rsc -ServiceAccountFile $SAFile + Write-Log "INFO: Connected to Rubrik Security Cloud." + + $query = New-RscQuery -GqlQuery slaManagedVolumes -AddField Nodes.HostDetail, Nodes.SmbShare, Nodes.ClientConfig, Nodes.ClientConfig.BackupScript, Nodes.ClientConfig.PreBackupScript + $query.var.filter = @(Get-RscType -Name Filter) + $query.var.filter[0].field = "NAME_EXACT_MATCH" + $query.var.filter[0].Texts = $MvName + $mvDetail = $query.Invoke().nodes[0] + + $paths = $mvDetail.ClientConfig.ChannelHostMountPaths + Write-Log "INFO: Retrieved paths: $($paths -join ', ')" +} catch { + Write-Log "ERROR: Failed to retrieve paths from Rubrik. $($_.Exception.Message)" + exit 1 +} + +$directoryParam = $paths -join ', ' + +# Validate job count +if ($Jobs -lt 1 -or $Jobs -gt 8) { + Write-Host "ERROR: Jobs parameter must be between 1 and 8. Provided: $Jobs" + exit 1 +} + +Write-Host "INFO: Starting $Jobs parallel backup jobs" + +$today = (Get-Date).Date + function Get-BackupType($directoryParam) { # Use first directory to check flags (assuming shared flag logic across all directories) $firstDir = ($directoryParam -split ',')[0].Trim()