Private/SqlDatabasesFunctions.ps1
function ImportSqlServerAssemblies { [CmdletBinding()] Param ( [string]$LogFile ) WriteLog -Path $LogFile -Message "Loading SQL Server assemblies" [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null WriteLog -Path $LogFile -Message "Microsoft.SqlServer.SMO loaded" [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null WriteLog -Path $LogFile -Message "Microsoft.SqlServer.SmoExtended loaded" } function Write-ProgressMessage { if ( $_.Percent -ne 100) { Write-Host $_.Percent "%" } else { Write-Host $_.Percent "% Please, wait..." } } function BackupSqlDatabase { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$DatabaseName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [ValidateScript({ if (Test-Path $_) { $true } else { Throw "'$_' path is not valid. Please, provide a valid value" }})] [string]$BackupDirectory, [string]$LogFile ) $sqlServer = New-Object -TypeName Microsoft.SQLServer.Management.Smo.Server($SqlServerName) $timeStamp = Get-Date -format yyyy-MM-dd-HHmmss $targetPath = Join-Path -Path $backupDirectory -ChildPath ($DatabaseName + "_" + $timeStamp + ".bak") $smoBackup = New-Object -TypeName "Microsoft.SqlServer.Management.Smo.Backup" $smoBackup.Action = "Database" $smoBackup.BackupSetDescription = "Full Backup of " + $DatabaseName $smoBackup.BackupSetName = $DatabaseName + " Backup" $smoBackup.Database = $DatabaseName $smoBackup.MediaDescription = "Disk" $smoBackup.Devices.AddDevice($targetPath, "File") $percentEventHandler = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] { Write-ProgressMessage } $completedEventHandler = [Microsoft.SqlServer.Management.Common.ServerMessageEventHandler] { Write-Host "Database '$DatabaseName' backed up successfully" } $smoBackup.add_PercentComplete($percentEventHandler) $smoBackup.add_Complete($completedEventHandler) $smoBackup.SqlBackup($sqlServer) WriteLog -Path $LogFile -Message "OUTPUT: Database Backup finished for '$DatabaseName' ($SqlServerName)" WriteLog -Path $LogFile -Message "OUTPUT: Backup location '$targetPath'" } function BackupSqlDatabases { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$DatabaseNames, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BackupDirectory, [string]$LogFile ) ConfirmSqlServerConnection -SqlServerName $SqlServerName -LogFile $LogFile WriteLog -Path $LogFile -Message "Checking the most recent backups" $sqlDatabasesBackups = Join-Path -Path $BackupDirectory -ChildPath "SqlDatabasesBackups" $backupDir = GetLastCreatedDir -Directory $sqlDatabasesBackups if([String]::IsNullOrWhiteSpace($backupDir)) { WriteLog -Path $LogFile -Message "No backups available" WriteLog -Path $LogFile -Message "Creating unique backup directory" $backupDir = NewUniqueDir -Directory $BackupDirectory -SubDirectory "SqlDatabasesBackups" WriteLog -Path $LogFile -Message "Unique backup directory created '$backupDir'" } else { WriteLog -Path $LogFile -Message "Most recent backups found in '$backupDir'" #if Logs folder is available, that means that, there were problems with the most recent backups $logsDirectory = GetLogDir -Directory $backupDir if ($logsDirectory) { WriteLog -Path $LogFile -Message "WARNING: The most recent backups were not finished successfully. Sitecore.Deployment will try to fix them" } else { WriteLog -Path $LogFile -Message "The most recent backups were finished successfully. Sitecore.Deployment will create new backups for you" WriteLog -Path $LogFile -Message "Creating unique backup directory" $backupDir = NewUniqueDir -Directory $BackupDirectory -SubDirectory "SqlDatabasesBackups" WriteLog -Path $LogFile -Message "Unique backup directory created '$backupDir'" } } WriteLog -Path $LogFile -Message "'$backupDir' directory will be used for storing the current backups" $dbNamesArray = $DatabaseNames.Split(',').Trim() if ($dbNamesArray.Count -gt 0) { ImportSqlServerAssemblies -LogFile $LogFile $lastBackups = Get-ChildItem -Path $backupDir -Filter *.bak $failedDatabases = @() foreach ($dbName in $dbNamesArray) { try { foreach($backup in $lastBackups) { if ($backup.Name.StartsWith($dbName)) { RemoveItem -Path $backup.FullName } } WriteLog -Path $LogFile -Message "OUTPUT: Database Backup started for '$dbName' ($SqlServerName). Please, wait" BackupSqlDatabase -SqlServerName $SqlServerName -DatabaseName $dbName -BackupDirectory $backupDir -LogFile $LogFile } catch { WriteLog -Path $LogFile -Message "ERROR: Backup failed for '$dbName' ($SqlServerName)" -RedirectStream $true $exMessage = $_.Exception | Format-List -Force | Out-String WriteLog -Path $LogFile -Message "VERBOSE_ERROR: $exMessage" if ($dbNamesArray.IndexOf($dbName) -ne $dbNamesArray.Count - 1) { WriteLog -Path $LogFile -Message "WARNING: Continue with next database" -RedirectStream $true } $failedDatabases += $dbName } } if ($failedDatabases.Count -gt 0) { NewLogDir -Directory $backupDir Throw "Back up failed for '$failedDatabases'. Please, try to fix this issue before proceed with next deployment steps" } else { $logsDirectory = GetLogDir -Directory $backupDir if ($logsDirectory) { RemoveItem -Path $logsDirectory } } } } function BackupSqlDatabasesRemotely { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$DatabaseNames, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BackupDirectory, $VerboseRemote ) try { $VerbosePreference = $VerboseRemote Write-Verbose "VERBOSE: Importing 'Sitecore.Deployment'" *>&1 Import-Module -Name Sitecore.Deployment -ErrorAction Stop Write-Verbose "VERBOSE: 'Sitecore.Deployment' imported" *>&1 Backup-SqlDatabases -SqlServerName $SqlServerName -DatabaseNames $DatabaseNames -BackupDirectory $BackupDirectory } catch { Write-Warning "ERROR: 'Sitecore.Deployment' is not installed" *>&1 $message = $_.Exception | Format-List -Force | Out-String Write-Verbose "VERBOSE_ERROR: $message" *>&1 } } function RestoreSqlDatabase { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$DatabaseName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [ValidateScript({ if (Test-Path $_) { $true } else { Throw "'$_' path is not valid. Please, provide a valid value" }})] [string]$BackupPath, [string]$LogFile ) $sqlServer = New-Object -TypeName Microsoft.SQLServer.Management.Smo.Server($SqlServerName) $smoRestore = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Restore $smoRestore.Database = $DatabaseName $smoRestore.ReplaceDatabase = $true $smoRestore.NoRecovery = $false $smoRestore.Action = "Database" $smoRestore.Devices.AddDevice($BackupPath, "File") $smoRestoreDataFile = New-Object -TypeName Microsoft.SqlServer.Management.Smo.RelocateFile $defaultData = $sqlServer.DefaultFile if (($defaultData -eq $null) -or ($defaultData -eq $empty)) { $defaultData = $sqlServer.MasterDBPath } $smoRestoreDataFile.PhysicalFileName = Join-Path -Path $defaultData -ChildPath ($DatabaseName + "_Data.mdf") $smoRestoreLogFile = New-Object -TypeName Microsoft.SqlServer.Management.Smo.RelocateFile $defaultLog = $sqlServer.DefaultLog if (($defaultLog -eq $null) -or ($defaultLog -eq $empty)) { $defaultLog = $sqlServer.MasterDBLogPath } $smoRestoreLogFile.PhysicalFileName = Join-Path -Path $defaultLog -ChildPath ($DatabaseName + "_Log.ldf") $dbFileList = $smoRestore.ReadFileList($sqlServer) $smoRestoreDataFile.LogicalFileName = $dbFileList.Select("Type = 'D'")[0].LogicalName $smoRestoreLogFile.LogicalFileName = $dbFileList.Select("Type = 'L'")[0].LogicalName $smoRestore.RelocateFiles.Add($smoRestoreDataFile) | Out-Null $smoRestore.RelocateFiles.Add($smoRestoreLogFile) | Out-Null if ($sqlServer.Databases[$DatabaseName] -ne $null) { $sqlServer.KillAllProcesses($DatabaseName) } $percentEventHandler = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] { Write-ProgressMessage } $completedEventHandler = [Microsoft.SqlServer.Management.Common.ServerMessageEventHandler] { Write-Host "Database '$DatabaseName' restored successfully" } $smoRestore.add_PercentComplete($percentEventHandler) $smoRestore.add_Complete($completedEventHandler) $smoRestore.SqlRestore($sqlServer) WriteLog -Path $LogFile -Message "OUTPUT: Database Restore finished for '$DatabaseName' ($SqlServerName)" } function RestoreSqlDatabases { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$DatabaseNames, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BackupDirectory, [string]$LogFile ) ConfirmSqlServerConnection -SqlServerName $SqlServerName -LogFile $LogFile WriteLog -Path $LogFile -Message "Getting the most recent backups" $sqlDatabasesBackups = Join-Path -Path $BackupDirectory -ChildPath "SqlDatabasesBackups" $lastBackupsFolder = GetLastCreatedDir -Directory $sqlDatabasesBackups if([String]::IsNullOrWhiteSpace($lastBackupsFolder)) { Throw "No backups found" } WriteLog -Path $LogFile -Message "Most recent backups found in '$lastBackupsFolder'" $dbNamesArray = $DatabaseNames.Split(',').Trim() if ($dbNamesArray.Count -gt 0) { ImportSqlServerAssemblies -LogFile $LogFile $missingBackups = @() $lastBackups = Get-ChildItem -Path $lastBackupsFolder -Filter *.bak foreach ($dbName in $dbNamesArray) { foreach($backup in $lastBackups) { if ($backup.Name.StartsWith($dbName)) { continue } $missingBackups += $dbName } } if ($missingBackups.Count -gt 0) { Throw "Restore mismatch. No backups were restored. Backups of the following databases are missing '$missingBackups'. Please, make the restore of all databases manually. Last available Sitecore.Deployment backups are located in '$lastBackupsFolder'" } $failedDatabases = @() foreach($backup in $lastBackups) { try { WriteLog -Path $LogFile -Message "OUTPUT: Database Restore started for '$dbName' ($SqlServerName). Please, wait" RestoreSqlDatabase -SqlServerName $SqlServerName -DatabaseName $dbName -BackupPath $backup.FullName -LogFile $LogFile } catch { WriteLog -Path $LogFile -Message "ERROR: Restore failed for '$dbName' ($SqlServerName)" -RedirectStream $true $exMessage = $_.Exception | Format-List -Force | Out-String WriteLog -Path $LogFile -Message "VERBOSE_ERROR: $exMessage" if ($dbNamesArray.IndexOf($dbName) -ne $dbNamesArray.Count - 1) { WriteLog -Path $LogFile -Message "WARNING: Continue with next database" -RedirectStream $true } $failedDatabases += $dbName } } if ($failedDatabases.Count -gt 0) { Throw "Restore failed for '$failedDatabases'. Please, check the backups or try to restore them manually" } } } function RestoreSqlDatabasesRemotely { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SqlServerName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$DatabaseNames, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$BackupDirectory, $VerboseRemote ) try { $VerbosePreference = $VerboseRemote Write-Verbose "VERBOSE: Importing 'Sitecore.Deployment'" *>&1 Import-Module -Name Sitecore.Deployment -ErrorAction Stop Write-Verbose "VERBOSE: 'Sitecore.Deployment' imported" *>&1 Restore-SqlDatabases -SqlServerName $sqlServerName -DatabaseNames $DatabaseNames -BackupDirectory $BackupDirectory } catch { Write-Warning "ERROR: 'Sitecore.Deployment' is not installed" *>&1 $message = $_.Exception | Format-List -Force | Out-String Write-Verbose "VERBOSE_ERROR: $message" *>&1 } } |