It is all to easy to build a database on a server and then expect it to be fully functional. Not so fast: There are a number of possibilities in terms of server-based functionality that can defeat you. Databases are likely to have scheduled jobs and alerts, and may have server triggers or even message queues. They are going to have different credentials and logins, proxy accounts, shared schedules and so on. Database applications will sometimes consist of several database components or may require linked servers to be set up. For this reason, it is important to be certain what this server-based functionality is, where it is, and to ensure that it is scripted out into source control so it can be used when provisioning a server for the application.
I’ve written in the past about how to script out server code. However, there is a different problem which is to work out the difference in configuration between two servers. Once you know the differences between the server configuration of a SQL Server instance that holds a working database application and the instance on which you wish to build a version of the application, then you can produce the relevant scripts to provide the database with the required working environment. This is a fairly common DevOps requirement, but one that seems to have few tools to help with the task. The possible reasons for this become apparent as soon as you look at the settings, properties and attributes of the server. Not only are there a great number of them, but few are likely to be relevant. When you are just starting out with this problem, It is much better to have an overview of the differences rather than become overwhelmed with a tsunami of possibly irrelevant data.
To get an overview, I prefer to examine what SMO regards as the server collections. These include such things as databases, endpoints, agent jobs, alerts and linked servers. The simplest comparisons are on the actual names. This tells you whether the two servers have, for example, a linked server of the same name. It is a start, but two servers can have, say, the same agent job with the same name, but that do different things, or are at different versions. You will still need to script them out and check that the scripts are the same, though you should then beware of false negatives due to headers with dates in them.-scripts will show up as different when the difference is actually just the date you did the scripting!
There is an important distinction to be made. The server can consist of a range of objects, such as a database. If we examine the name, we can tell fairly well what databases should be on the server we are provisioning. However, if you compare two databases, you can say whether the names are the same or different, but we are not saying whether the databases with the same name are identical or different. It is the same with settings: we can say that servers have the same settings, but they may have wildly different values. Now the fact that they have different values may, or may not, be important to the provisioning process. It would be foolhardy to say what is important because that will depend on your circumstances. After all, the fact that the server has a different name is very unlikely to be interesting.
We therefore will concentrate, in this script, on comparing the names of the objects in the various collections. This will at least tell you if a component is missing and is a lot quicker than tooling about with SSMS’s object browser!
For this work, there is a useful built-in Cmdlet called Compare-Object. Once you’ve understood the way it works it is very handy for doing comparisons. Its only problem is that it is not the most intuitive visual way of reporting differences, so we use the Compare-Object’s ‘SideIndicator’ for building up the results and then convert the results in one go, into something that is easier to understand. Note that I’m not listing objercts that are equal, just those that exist only on one server or the other. That is a knob you can twiddle but I’d only do that where there are likely to be just a limited number of objects.
Obviously, this would be wrapped into a function in general use. I’ve unwrapped it here to make it easier to investigate.
$Data = @{ "source" = @{ #this is the source server server you are comparing 'Server' = 'MySourceServer'; #The SQL Server instance 'instance' = '\' 'username' = 'ArthurGig'; #leave blank if windows authentication } "target" = @{ #this is the server you are comparing it with 'Server' = 'MyTargetServer'; #The SQL Server instance 'instance' = '\' 'username' = 'PhilipJFactor'; #leave blank if windows authentication } } $BadChars = '[\\\/\:\.]' #characters that we don't want in filenames set-psdebug -strict # to catch subtle errors $ErrorActionPreference = "stop" # you can opt to stagger on, bleeding, if an error occurs # Load sqlserver module $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 if ([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Management.XEvent") -eq $null) { throw "Could not load library for Extended Events." } $VerbosePreference = $popVerbosity <# ---- now pick up the credentials if using SQL Server Authentication #> @($Data.source,$Data.target)|foreach{ # get credentials if necessary if ($_.username -ne '') #then it is using SQL Server Credentials { #have we got them stored locally $SqlEncryptedPasswordFile = ` "$env:USERPROFILE\$($_.username)-$($_.Server+$_.Instance -replace $BadChars, '').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($SqlUserName, $Sqlencrypted) } else #then we have to ask the user for it { #hasn't got this set for this login $SqlCredentials = get-credential -Credential $SqlUserName $SqlCredentials.Password | ConvertFrom-SecureString | Set-Content $SqlEncryptedPasswordFile } $_.Credentials=$SqlCredentials #save them with the server data } } $ms = 'Microsoft.SqlServer' $My = "$ms.Management.Smo" # <# now we use the information we have and the credentials to connect to the servers #> @($Data.source,$Data.target)|foreach{ if ($_.username -eq '') #dead simple if using windows security { $s = new-object ("$My.Server") $_.Server+$_.Instance } else # if using sql server security we do it via a connection object { $ServerConnection = new-object "$ms.Management.Common.ServerConnection" ( "$($_.Server)$($_.Instance)" , $_.username, $_.Credentials.Password) $s = new-object ("$My.Server") $ServerConnection } $_.ServerObject=$s } $sourceName=$Data.source.ServerObject.Name #for the result headings $TargetName=$Data.Target.ServerObject.Name #for the result headings <# now we start by collecting all the possible properties, excluding any that we know give trouble. and we go through each collection, comparing the names between the two servers #> $ComparisonList=$Data.source.ServerObject|gm -MemberType 'property'| where {$_.definition -like '*collection*'}| #all the collection objects select name | #filter out the ones that cause problems where {$_.Name -notin @('SystemMessages','OleDbProviderSettings')}| foreach { #for each collection name ... $currentType=$_.Name #we now get a list of objects for each $sourceList=$Data.source.ServerObject.$currentType|select name $TargetList=$Data.target.ServerObject.$currentType|select name #we check that they both have objects in the list if ($SourceList -ne $null -and $TargetList -ne $null) {Compare-Object $sourceList $TargetList -Property name| select name,SideIndicator, @{Name="Type"; Expression = {$CurrentType}}} elseif ($TargetList -ne $null) # well it is easy, only in the source { $TargetList| select Name, @{Name="SideIndicator"; Expression = {'=>'}}, @{Name="Type"; Expression = {$CurrentType}}} elseif ($SourceList -ne $null) # we know they are only in the target { $SourceList| select Name, @{Name="SideIndicator"; Expression = {'<='}}, @{Name="Type"; Expression = {$CurrentType}}} } #Now we get all the collections in the jobserver $ComparisonList+=$Data.source.ServerObject.Jobserver|gm -MemberType 'property'| where {$_.definition -like '*collection*'} | #get all the jobserver collection objects # all the Agent objects we want to script out Foreach { $currentType=$_.Name #get a list of all the objects $sourceList=$Data.source.ServerObject.JobServer.$currentType|select name $TargetList=$Data.target.ServerObject.JobServer.$currentType|select name if ($SourceList -ne $null -and $TargetList -ne $null) {Compare-Object $sourceList $TargetList -Property name| select name,SideIndicator, @{Name="Type"; Expression = {$CurrentType}}} elseif ($TargetList -ne $null) { $TargetList| select Name, @{Name="SideIndicator"; Expression = {'=>'}}, @{Name="Type"; Expression = {$CurrentType}}} elseif ($SourceList -ne $null) { $SourceList| select Name, @{Name="SideIndicator"; Expression = {'<='}}, @{Name="Type"; Expression = {$CurrentType}}} } # finally, we pick up the XEvents which are stored separately $Data.source.SqlConn = $Data.source.ServerObject.ConnectionContext.SqlConnectionObject $Data.Target.SqlConn = $Data.Target.ServerObject.ConnectionContext.SqlConnectionObject $Data.Source.XEstore = New-Object Microsoft.SqlServer.Management.XEvent.XEStore $Data.Source.SqlConn $Data.Target.XEstore = New-Object Microsoft.SqlServer.Management.XEvent.XEStore $Data.Target.SqlConn $sourceList=$Data.source.XEStore.Sessions | select name $TargetList=$Data.Target.XEStore.Sessions | select name if ($SourceList -ne $null -and $TargetList -ne $null) {$ComparisonList+= Compare-Object $sourceList $TargetList -Property name| select name,SideIndicator, @{Name="Type"; Expression = {'Xevent Sessions'}}} elseif ($TargetList -ne $null) { $ComparisonList+= $TargetList| select Name, @{Name="SideIndicator"; Expression = {'=>'}}, @{Name="Type"; Expression = {'Xevent Sessions'}}} elseif ($SourceList -ne $null) { $ComparisonList+= $SourceList| select Name, @{Name="SideIndicator"; Expression = {'<='}}, @{Name="Type"; Expression = {'Xevent Sessions'}}} <# Now we have the entire list we then list them out.#> $ComparisonList| select @{Name=$sourceName; Expression = {if ($_.sideIndicator -eq '<=') {$_.Name} else {''}}}, @{Name=$TargetName; Expression = {if ($_.sideIndicator -eq '=>') {$_.Name} else {''}}}, @{Name="Type"; Expression = {$_.Type}}
Here is a result from running it on a couple of development servers. The first two columns have the names of the two servers at the top, and the third column has the type of object we’re investigating
DeepThink\ BigThought\ Type ---------- ----------- ---- Nell Credentials Dan McGrew Credentials Abednego Databases Antipas Databases Archaelus Databases Daniel Databases Meshach Databases RedGateMonitor Databases ServerEvents Databases WebsiteUsage Databases AdventureWorks2012 Databases contacts Databases Customers Databases MarineMammals Databases NorthWind Databases WSLSOURCE LinkedServers ReportingServer LinkedServers Wheezy LinkedServers BIGTHOUGHT\Administrator Logins BIGTHOUGHT\Nell Logins BIGTHOUGHT\Posh Logins PercyTheGreenEngine Logins MSSecurityMtr Logins DEEPTHINK\Administrator Logins DEEPTHINK\Nell Logins HostDistribution Properties HostRelease Properties HostServicePackLevel Properties HostSku Properties RG_SQLLighthouse_DDLTrigger Triggers 208 Error Alerts InvalidObjectError Alerts Business JobCategories CheckClones Jobs CheckConfiguration Jobs InvalidObjectDetected Jobs RunPowerShellScript Jobs Nell ProxyAccounts PoshProxy ProxyAccounts RunItEveryMinute SharedSchedules AllErrors Xevent Sessions allLogins Xevent Sessions AllSQLStatementsExecuted Xevent Sessions AllWarnings Xevent Sessions BatchCompleted Xevent Sessions CheckingSPsAndSQLStatements Xevent Sessions MonitorErrors Xevent Sessions PermissionsErrors Xevent Sessions QueryTimeouts Xevent Sessions QuickSessionStandard Xevent Sessions QuickSessionTSQL Xevent Sessions sqlmonitor_session Xevent Sessions UncompletedQueries Xevent Sessions WhoChangedWhat Xevent Sessions MonitorSuspiciousErrors Xevent Sessions Recompile_Histogram Xevent Sessions Recompiles Xevent Sessions
The post Comparing SQL Server Instances: Objects by Name appeared first on Simple Talk.
from Simple Talk http://bit.ly/2UTFQNB
via
No comments:
Post a Comment