Skip to content

Instantly share code, notes, and snippets.

@Phil-Factor
Last active February 20, 2024 16:12
Show Gist options
  • Save Phil-Factor/c4338c244d1d9a71742a98127d4ab035 to your computer and use it in GitHub Desktop.
Save Phil-Factor/c4338c244d1d9a71742a98127d4ab035 to your computer and use it in GitHub Desktop.
This routine uses SQL Clone to create a whole group of databases on a number of SQL Servers or instances. This routine will create images only if they don't already exist. However, this routine will always drop existing clones and recreate a new one, from its current image, even if one of those clones has a day's work unsaved on one it.
<# first we fill in the data object that contains all the data we need, such as the list of databases we want copied and the list of servers we want to copy them to. #>
$Data = @{
'ServerURL' = 'http://MyCloneServer:14145'; #the HTTP address of the Clone Server
'ImageDirectoryURL' = '\\TheFileServer\Directory'; #the URL of the image directory
"Original" = @{
#We will clone from this database. This is the original, maybe a build stocked with data
'Server' = 'MyBuildServer'; #The SQL Server instance
'instance' = '\'
'username' = 'PhilFactor'; #leave blank if windows authentication
'DatabasesToCopy' = @('*', ) # the list of databases we want copied, perhaps
#with wildcards in the list. We make sure that each resulting database is only done once
} #here is our list of servers we want as destinations
"Copies" = @(
@{
#We will clone to these servers
'Server' = 'MyFirstServ'; #The SQL Server instance
},
@{
#We will clone to these servers
'Server' = 'MyOtherServer'; #The other SQL Server instance
}
)
}
set-psdebug -strict # to catch subtle errors
$ErrorActionPreference = "stop" # you can opt to stagger on, bleeding, if an error occurs
# We now the Load sqlserver module. We need this to enumerate the original databases
$popVerbosity = $VerbosePreference #remember current verbosity setting
$VerbosePreference = "Silentlycontinue"
# the import process is very noisy if you are in verbose mode
Import-Module sqlserver -DisableNameChecking #load the SQLPS functionality
# get credentials if necessary
if ($data.Original.username -ne '') #then it is using SQL Server Credentials
{
$SqlEncryptedPasswordFile = `
"$env:USERPROFILE\$($data.Original.username)-$($data.Original.Server).txt"
# test to see if we know about the password in a secure string stored in the user area
if (Test-Path -path $SqlEncryptedPasswordFile -PathType leaf)
{
#has already got this set for this login so fetch it
$Sqlencrypted = Get-Content $SqlEncryptedPasswordFile | ConvertTo-SecureString
$SqlCredentials = `
New-Object System.Management.Automation.PsCredential($data.Original.username, $Sqlencrypted)
}
else #then we must ask the user for it
{
#hasn't got this set for this login
$SqlCredentials = get-credential -Credential $data.Original.username
$SqlCredentials.Password | ConvertFrom-SecureString |
Set-Content $SqlEncryptedPasswordFile
}
}
# now we collect up the names of the database on the source server and
# gather up a list of database names
$ms = 'Microsoft.SqlServer'
$My = "$ms.Management.Smo" #
if ($data.Original.username -eq '') #dead simple if using windows security
{ $s = new-object ("$My.Server") $data.Original.Server }
else # if using sql server security we do it via a connection object
{
$ServerConnection = new-object "$ms.Management.Common.ServerConnection" (
$data.Original.Server, $data.Original.username, $SqlCredentials.Password)
$s = new-object ("$My.Server") $ServerConnection
}
Connect-SqlClone -ServerUrl $Data.ServerURL # make a connectiuon to SQL Clone
$TheCloneServer = Get-SqlCloneSqlServerInstance -MachineName $data.Original.Server
if ($TheCloneServer -eq $null)
{
write-error "sorry but $($data.Original.Server
) isn't on the list of clone servers. Have you installed the agent?"
}
#now turn the wildcard list into a unique sorted array
$possibilities = $s.Databases | select name
$DatabaseList = $data.Original.DatabasesToCopy |
where { $_ -Notlike '*[*?]*' } |
where { $possibilities.Name -contains $_ }
$DatabaseList += $data.Original.DatabasesToCopy |
where { $_ -like '*[*?]*' } | foreach{
$wildcard = $_; $possibilities.Name |
where { $_ -like $wildcard }
}
$DatabaseList | Sort-Object -Unique |
Foreach{
Write-Verbose "checking for image of $($_)"
$DbName = $_; #
<# If the image already exists, then use it, else create the image. If you need to
update the image, then delete the image and all the clones using it
before running the script #>
# first test to see if the image is there
$Image = Get-SqlCloneImage |
where { ($_.OriginServerName -eq "$(
$data.Original.Server)$($data.Original.instance)") -and (
$_.OriginDatabaseName -eq "$DbName") }
if ($image -ne $null) #then we succeeded
{
write-verbose "Existing image called $(
$Image.Name) from $(
$Image.OriginServerName) on $(
$Image.CreatedDate)"
}
else # tut. No image. We have to create it
{
$AllArgs = @{
'Name' = "$($DbName)Image"; #what is specified for its name in the data file
'SqlServerInstance' = $TheCloneServer;
# we fetch the SqlServerInstanceResource for passing to the New-SqlCloneImage cmdlets.
'DatabaseName' = $DbName; #the name of the database
'Destination' = (Get-SqlCloneImageLocation |
Where Path -eq $data.ImageDirectoryURL) #where the image is stored
}
<# do we need any image modifications? #>
if ($Data.ImageModifications -ne $null)
{ #we need to modify it
$ImageChangeScript = @();
$Data.ImageModifications.GetEnumerator() | foreach{
if ($_.Name -eq $Dbname) {$ImageChangeScript += New-SqlCloneSqlScript -Path $_.Value;}
}
$AllArgs += @{ 'Modifications' = $ImageChangeScript }
}
# Start creating a new image from a live database
$ImageOperation = New-SqlCloneImage `
@AllArgs -ErrorAction silentlyContinue -ErrorVariable +Errors `
# gets the ImageResource which then enables us to wait until the process is finished
write-verbose "Creating the image called $(
$AllArgs.Name) from $(
$Dbname) on $(
$server)"
Wait-SqlCloneOperation -Operation $ImageOperation
}
}
#for each server we specify
$data.Copies | foreach{
$CloneToDo = $_
$Clonelocation = Get-SqlCloneSqlServerInstance |
Where server -ieq $CloneToDo.Server
if ($Clonelocation -eq $null) { write-error "$($CloneToDo.Server) doesn't seem to be a clone server" }
#for each database we've asked for ...
$DatabaseList | Sort-Object -Unique | foreach {
$Dbname = $_
#now we get the image
$CorrectImage = Get-SqlCloneImage -Name "$($DbName)Image"
if ($CorrectImage -eq $null) #this is impossible (gulp)
{ Write-Error "$($DbName)Image was missing" }
if ($correctImage.GetType().Name -ne 'ImageResource')
{ Write-Error "$($DbName)Image name was ambiguous" }
<# does the clone we want exist? #>
$clone = Get-SqlClone `
-ErrorAction silentlyContinue `
-Name "$($Dbname)" `
-Location $Clonelocation
<# If the clone does exist then zap it #>
if (($clone) -ne $null) #one already exists!
{
write-warning "Removing Clone $(
$Dbname) that already existed on $($CloneToDo.Server)"
Remove-SqlClone $clone | Wait-SqlCloneOperation
}
<# Now Create the clone #>
$AllArgs = @{
'Name' = $Dbname;
'Location' = $Clonelocation
}
write-verbose "Creating the clone called $(
$dbname) on $(
$CloneLocation.ServerFullyQualifiedDomainName)"
#now we actually create the clone
$CorrectImage |
New-SqlClone @Allargs |
Wait-SqlCloneOperation
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment