Skip to content

Instantly share code, notes, and snippets.

@Brad-Christie
Last active July 18, 2019 11:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Brad-Christie/8093b1e262f9dca62f9614e6fbfad8bd to your computer and use it in GitHub Desktop.
Save Brad-Christie/8093b1e262f9dca62f9614e6fbfad8bd to your computer and use it in GitHub Desktop.
Backup Sitecore Instance (Single Developer Only)
Function GetDatabases {
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory, ValueFromPipeline)]
[ValidateScript({ Test-Path $_ -PathType "Container" })]
[string]$WebRoot
)
Process {
$Databases = @()
Function TestIsSqlConnectionString([string]$ConnectionString) {
Try {
[void]([System.Data.SqlClient.SqlConnectionStringBuilder]::new($ConnectionString))
$true
} Catch {
$false
}
}
$connectionStringsPath = Join-Path $WebRoot -ChildPath "App_Config\ConnectionStrings.config"
If (!(Test-Path $connectionStringsPath)) {
Write-Warning "GetDatabases: ConnectionStrings.config '${connectionStringsPath}' does not exist."
Return $Databases
}
$connectionStringsXml = [xml](Get-Content $connectionStringsPath)
$connectionStrings = [System.Xml.XmlElement]$connectionStringsXml.SelectSingleNode("//connectionStrings")
If ($null -ne $connectionStrings) {
$connectionStrings.SelectNodes("add") | ForEach-Object {
$add = [System.Xml.XmlElement]$_
$connectionString = $add.GetAttribute("connectionString")
If (!(TestIsSqlConnectionString $connectionString)) {
Return # aka "Continue" in ForEach-Object
}
If (!([string]::IsNullOrWhiteSpace($connectionString))) {
$name = $add.GetAttribute("name")
If (!([string]::IsNullOrEmpty($name))) {
$sqlConnectionString = [System.Data.SqlClient.SqlConnectionStringBuilder]::new($connectionString)
$databaseName = $sqlConnectionString.InitialCatalog
$dataSource = $sqlConnectionString.DataSource
If (!([string]::IsNullOrWhiteSpace($databaseName))) {
$exist = $null -ne ($Databases | Where-Object { $_.DatabaseName -eq $databaseName -and $_.DataSource -eq $dataSource })
If (!$exist) {
Write-Host "AddDatabases: Adding ${databaseName}"
$Databases += @{
Name = $name
DatabaseName = $databaseName
DataSource = $dataSource
ConnectionString = $connectionString
}
} Else {
Write-Host "AddDatabases: Skipped ${databaseName} (${connectionString}) [Exists]"
}
}
}
}
}
$configSource = $connectionStrings.GetAttribute("ConfigSource")
If (!([string]::IsNullOrWhiteSpace($configSource))) {
$configSourcePath = Join-Path $WebRoot -ChildPath $configSource
If (!(Test-Path $configSourcePath)) {
Write-Warning "GetDatabases: ConfigSource '${configSourcePath}' does not exist."
Return $Databases
}
}
} Else {
Write-Warning "GetDatabases: connectionStrings not found."
}
Return $Databases
}
}
Function BackupDatabase {
[CmdletBinding()]
Param(
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[psobject[]]$Database
,
[Parameter(Position = 0, Mandatory)]
[ValidateScript({ Test-Path $_ -IsValid })]
[string]$Destination
,
[Parameter()]
[Alias("Username", "SAUsername")]
[string]$DatabaseAdminUsername = "sa"
,
[Parameter()]
[Alias("Password", "SAPassword")]
[string]$DatabaseAdminPassword
)
Process {
If (!(Test-Path $Destination)) {
New-Item $Destination -ItemType "Directory" | Out-Null
}
$databaseName = $Database.DatabaseName
$connectionString = $Database.ConnectionString
$backupFilePath = Join-Path $Destination -ChildPath ("${databaseName}_{0}.bak" -f (Get-Date -UFormat "%Y%m%d"))
$backupName = "{0} backup {1}" -f $databaseName, (Get-Date -UFormat "%Y%m%d")
$query = "BACKUP DATABASE [${databaseName}] TO " +
"DISK = N'${backupFilePath}' WITH NOFORMAT, NOINIT, " +
"NAME = N'${backupName}', SKIP, NOREWIND, NOUNLOAD, STATS = 10"
If (!([string]::IsNullOrWhiteSpace($DatabaseAdminPassword))) {
$connectionStringBuilder = [System.Data.SqlClient.SqlConnectionStringBuilder]::new($connectionString)
$connectionStringBuilder["User ID"] = $DatabaseAdminUsername
$connectionStringBuilder.Password = $DatabaseAdminPassword
$connectionString = $connectionStringBuilder.ToString()
}
Write-Host "Backing up ${DatabaseName} to ${backupFilePath}..."
Try {
Invoke-Sqlcmd -Query $query -ConnectionString $connectionString
} Catch {
Write-Warning "Unable to backup ${databaseName}: $($_.Exception.Message)"
}
}
}
Function BackupWebsite {
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory, ValueFromPipeline)]
[ValidateScript({ Test-Path $_ -PathType "Container" })]
[string]$WebRoot
,
[Parameter(Position = 1, Mandatory)]
[ValidateScript({ Test-Path $_ -PathType "Container" })]
[string]$DestinationPath
)
Process {
$backupFilePath = Join-Path $DestinationPath -ChildPath (Get-Date -UFormat "Website_%Y%m%d.zip")
Get-ChildItem $WebRoot -Recurse | Compress-Archive -DestinationPath $backupFilePath -CompressionLevel "Optimal"
}
}
. "${PSScriptRoot}\Backup-Sitecore.ps1"
$WebRoot = "C:\inetpub\wwwroot\habitathome.dev.local"
$BackupPath = Join-Path $env:TEMP -ChildPath (New-Guid).ToString("d")
$SAUsername = "SA"
$SAPassword = "Str0NgPA33w0rd!!"
Try {
New-Item $BackupPath -ItemType Directory | Out-Null
# Give SQL permissions to the directory otherwise you get access is denied
$acl = Get-Acl $BackupPath
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("NT SERVICE\MSSQLSERVER","FullControl","Allow")
$acl.SetAccessRule($accessRule)
$acl | Set-Acl $BackupPath
# Appears SC's WDP (when creating SQL accounts) doesn't add DB_BACKUPOPERATOR role
# so we need to leverage SA account when performing backups
# Backs up DBs to DBNAME_YYMMDD.bak
GetDatabases $WebRoot | BackupDatabase -Destination $BackupPath -DatabaseAdminUsername $SAUsername -DatabaseAdminPassword $SAPassword
# Backs up website directory to Website_YYMMDD.zip
BackupWebsite $WebRoot -DestinationPath $BackupPath
} Finally {
Get-ChildItem "${BackupPath}\*" -Recurse | Write-Output
# Cleanup while developing...(using $env:TEMP)
#Remove-item $BackupPath -Recurse
}
@Brad-Christie
Copy link
Author

Brad-Christie commented Jul 18, 2019

Future Enhancements:

  • Add SOLR backup (leverage backup API)
  • Unload/stop website while backing up
  • Ignore unnecessary items (cache, etc.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment