<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Stéphane Savorgnano, auteur/autrice sur dbi Blog</title>
	<atom:link href="https://www.dbi-services.com/blog/author/stephane-savorgnano/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.dbi-services.com/blog/author/stephane-savorgnano/</link>
	<description></description>
	<lastBuildDate>Tue, 30 Sep 2025 14:09:40 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/cropped-favicon_512x512px-min-32x32.png</url>
	<title>Stéphane Savorgnano, auteur/autrice sur dbi Blog</title>
	<link>https://www.dbi-services.com/blog/author/stephane-savorgnano/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>How to patch SQL Server instances with Qualys Patch Management</title>
		<link>https://www.dbi-services.com/blog/how-to-patch-sql-server-instances-with-qualys-patch-management/</link>
					<comments>https://www.dbi-services.com/blog/how-to-patch-sql-server-instances-with-qualys-patch-management/#respond</comments>
		
		<dc:creator><![CDATA[Stéphane Savorgnano]]></dc:creator>
		<pubDate>Tue, 30 Sep 2025 14:09:37 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Database management]]></category>
		<category><![CDATA[MS Teams]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[AlwaysOn]]></category>
		<category><![CDATA[patching]]></category>
		<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Qualy Patch Management]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=40478</guid>

					<description><![CDATA[<p>By one of our Customers, I have to use Qualys to patch their production SQL Server instances. And to be more precise the Qualys Patch Management application which provides a solution to manage vulnerabilities and deploy patches to secure and keep assets up-to-date.I will use the job functionality of Qualys Patch Management to automate the [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/how-to-patch-sql-server-instances-with-qualys-patch-management/">How to patch SQL Server instances with Qualys Patch Management</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>By one of our Customers, I have to use <a href="https://www.qualys.com/">Qualys</a> to patch their production SQL Server instances. And to be more precise the Qualys Patch Management application which provides a solution to manage vulnerabilities and deploy patches to secure and keep assets up-to-date.<br>I will use the job functionality of Qualys Patch Management to automate the patching based on a specific schedule.</p>



<p>My customer has the following context, a two nodes Always On active-active cluster with 4 instances where each instance owns between five and seven Availability Groups spread on the two cluster nodes.</p>



<p>The first step is to connect to the Qualys subscription of my customer:</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="922" height="469" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/image-18.png" alt="" class="wp-image-40483" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/image-18.png 922w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/image-18-300x153.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/image-18-768x391.png 768w" sizes="(max-width: 922px) 100vw, 922px" /></figure>



<p>And to navigate to the Patch Management application:</p>



<figure class="wp-block-image size-full"><img decoding="async" width="922" height="537" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/image-19.png" alt="" class="wp-image-40484" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/image-19.png 922w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/image-19-300x175.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/image-19-768x447.png 768w" sizes="(max-width: 922px) 100vw, 922px" /></figure>



<p>Once in the Patch Management, I go in the job menu to create the first job which will patch the first node of my Always On cluster.<br>As this job will be executed first it will have to execute the following tasks:</p>



<ul class="wp-block-list">
<li>Save the Availability group configuration for all instances in order to be able to re-dispatch the Availability Groups on both nodes once the patching will be finished</li>



<li>Fail-over all Availability Groups from the node server1 to the node server2</li>



<li>Patch the instances on node server1 with the available patches</li>



<li>Reboot the server</li>



<li>Fail-over all Availability Groups from the node server2 to the node server1</li>
</ul>



<p>In terms of Qualys job I have to create a new job with the following information:</p>



<ul class="wp-block-list">
<li>A name to identify the job, here SQLServer_server1_M_3rd_Saturday_2200 as this job will be executed the third Saturday of each month at 10PM</li>
</ul>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="633" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info1_noname-1024x633.jpg" alt="" class="wp-image-40488" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info1_noname-1024x633.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info1_noname-300x185.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info1_noname-768x475.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info1_noname.jpg 1429w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<ul class="wp-block-list">
<li>Select the asset (here the server) where the patches will be executed: server1</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="636" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info2_noname-1024x636.jpg" alt="" class="wp-image-40489" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info2_noname-1024x636.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info2_noname-300x186.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info2_noname-768x477.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info2_noname.jpg 1413w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<ul class="wp-block-list">
<li>Pre-actions: this step provides some actions which can be executed before to patch the assets. It can be: to run a script, install a software, change a registry key, uninstall a software or a system reboot.<br>I will use it to execute my PowerShell script to:<br><strong>1.</strong> Save the Availability Groups configuration in a JSON file (you can find here an example of this file below)<br><strong>2.</strong> fail-over the Availability Groups which are primary on node server1 to server2. The PowerShell script is also logging in a file</li>
</ul>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="527" height="882" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/JSON_File.jpg" alt="" class="wp-image-40490" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/JSON_File.jpg 527w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/JSON_File-179x300.jpg 179w" sizes="auto, (max-width: 527px) 100vw, 527px" /></figure>
</div>


<pre class="wp-block-code"><code>###Logging functions
Function Out-Log() {
param(
&#091;ValidateSet('INFO','WARNING','ERROR')]
&#091;String] $Type = 'INFO',
&#091;String] $Message
)

    '&#091;'+(Get-Date -f 'yyyy-MM-dd HH:mm:ss') +'] ' + ' - &#091;' + $Type + '] - ' + $Message | Out-File -FilePath $LogFile -Append;
}

Function Add-Warning() {
param(
&#091;String] $Warning
)
    If ($Warning) {
    Out-Log -Type WARNING -Message $Warning;
    }
}

Function Add-Error() {
param(
&#091;String] $Message
)
    Out-Log -Type ERROR -Message $Message;
}

###AGs functions
#Function to save Availability Group configuration in a JSON file
Function AGsSaveConfiguration(){
param (
&#091;Array] $instances,
&#091;String] $ConfigFile
)
    Out-Log -Type INFO "The following configurations have been saved:"
    $initialState = @()
    foreach ($instance in $instances) {
        $ags = Get-DbaAvailabilityGroup -SqlInstance $instance
        foreach ($ag in $ags) {
            $initialState += &#091;PSCustomObject]@{
                Instance = $instance
                AGName   = $ag.Name
                Primary  = $ag.PrimaryReplica
            }
            $output = "Instance = $instance, AGName = $($ag.Name), Primary  = $($ag.PrimaryReplica)"
            Out-Log -Type INFO "$output" 
        }
    }
    #Add configuration to a JSON file
    $initialState | ConvertTo-Json | Out-File $ConfigFile
    $output = $initialState | Format-Table
}

#Function to save Availability Group configuration in a JSON file
Function AGsRestoreConfiguration(){
param (
&#091;Array] $instances,
&#091;String] $ConfigFile
)
    if (!(Test-Path -Path $ConfigFile)) {
        Add-Error "Configuration file is missing"
        Add-Error "Exit without having restore the AG configurations"
        Return
    }

    $initialState = Get-Content $ConfigFile | ConvertFrom-Json

    foreach ($entry in $initialState) {
        write-host $entry
        $ag = Get-DbaAvailabilityGroup -SqlInstance $entry.Instance -AvailabilityGroup $entry.AGName
        if (($ag.PrimaryReplica -ne $entry.Primary) -and ($ag.LocalReplicaRole -eq "Secondary")) {
            Write-Host "Failover of Availability Group $($entry.AGName) to $($entry.Primary)"
            Out-Log -Type INFO "Failover of Availability Group $($entry.AGName) to $($entry.Primary)"
            Try {
                Invoke-DbaAgFailover -SqlInstance $entry.Instance -AvailabilityGroup $entry.AGName -Confirm:$false
            }
            Catch {
                Write-Host "Error during failover of Availability Group $($entry.AGName) to $($entry.Primary)"
                Add-Error "Error during failover of Availability Group $($entry.AGName) to $($entry.Primary)"                
            }
        }
    }
}

#Function to failover a list of instances to a specific host
Function AGsFailoverTo(){
param (
&#091;Array] $instances,
&#091;String] $TargetNode
)
    foreach ($instance in $instances) {
        $ags = Get-DbaAvailabilityGroup -SqlInstance $instance
        foreach ($ag in $ags) {
            if (($ag.ComputerName -eq $TargetNode) -and ($ag.LocalReplicaRole -eq "Secondary")) {
                Write-Host "Failover of $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                Out-Log -Type INFO "Failover of $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                Try {
                    Invoke-DbaAgFailover -SqlInstance $instance -AvailabilityGroup $ag.Name -Confirm:$false
                }
                Catch {
                    Write-Host "Error during failover of Availability Group $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                    Add-Error "Error during failover of Availability Group $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                }
            }
        }
    }
    Out-Log -Type INFO "All Availability Groups have been failover to node $TargetNode"

}

#LOG file path and name
$LogFileName = "AGsFailoverForPatching.txt"
$LogFilePath = "\\ShareFolder\LOG"
$LogFile = "$LogFilePath\$LogFileName"

#Configuration file path and name
$AGsConfigFileName = "initial_state.json"
$AGsConfigFilePath = "\\ShareFolder\LOG"
$AGsConfigFile = "$AGsConfigFilePath\$AGsConfigFileName"

###################################################################
#Save Availability Group configuration for all instances
Out-Log -Type INFO "******************** START PROCESS ********************"
Out-Log -Type INFO "START TO SAVE AVAILABILITY GROUPS CONFIGURATION"
Out-Log -Type INFO "Configuraton will be saved in file $AGsConfigFile"

#Find instances by list
$instances = @('server1\Instance1','server1\Instance2','server1\Instance3','server1\Instance4','server2\Instance1','server2\Instance2','server2\Instance3','server2\Instance4')
Out-Log -Type INFO "Instances available: $instances"

#Call the function AGsSaveConfiguration
AGsSaveConfiguration -instances $instances -ConfigFile $AGsConfigFile

Out-Log -Type INFO "Configurations saved successfully"

###################################################################
#Failover all Availability group to a specify node
Out-Log -Type INFO "START TO FAILOVER ALL AVAILABILITY GROUPS"
#Target node
$targetNode = "server2"
Out-Log -Type INFO "Target node: $targetNode"

#Call the function AGsFailoverTo
AGsFailoverTo -instances $instances -TargetNode $targetNode</code></pre>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="632" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info3_noname-1024x632.jpg" alt="" class="wp-image-40495" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info3_noname-1024x632.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info3_noname-300x185.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info3_noname-768x474.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info3_noname.jpg 1417w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<ul class="wp-block-list">
<li>Select the patches to apply to the assets, here we will select then automatically based on the filter patch.appFamily: SQL Server</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="484" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info6_noname-1024x484.jpg" alt="" class="wp-image-40496" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info6_noname-1024x484.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info6_noname-300x142.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info6_noname-768x363.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info6_noname-1536x726.jpg 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info6_noname.jpg 1836w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Qualys automatically detects that SQL Server 2019 instances are installed on this server as well as SQL Server Management Studio and provides the available patches.<br>Once patched have been applied, the server will automatically reboot.</p>



<ul class="wp-block-list">
<li>Post-actions: the possible post actions are: run a script, install a software, change a registry key or uninstall a software. As the instances have been patched and the server rebooted, we need to fail-over all Availability Groups which are primary on node server2 to server1, as the node server2 will be patched by the second job. A PowerShell script is then executed to do that:</li>
</ul>



<pre class="wp-block-code"><code>###Logging functions
Function Out-Log() {
param(
&#091;ValidateSet('INFO','WARNING','ERROR')]
&#091;String] $Type = 'INFO',
&#091;String] $Message
)
    '&#091;'+(Get-Date -f 'yyyy-MM-dd HH:mm:ss') +'] ' + ' - &#091;' + $Type + '] - ' + $Message | Out-File -FilePath $LogFile -Append;
}

Function Add-Warning() {
param(
&#091;String] $Warning
)
    If ($Warning) {
    Out-Log -Type WARNING -Message $Warning;
    }
}

Function Add-Error() {
param(
&#091;String] $Message
)
    Out-Log -Type ERROR -Message $Message;
}

###AGs functions
#Function to save Availability Group configuration in a JSON file
Function AGsSaveConfiguration(){
param (
&#091;Array] $instances,
&#091;String] $ConfigFile
)
    Out-Log -Type INFO "The following configurations have been saved:"
    $initialState = @()
    foreach ($instance in $instances) {
        $ags = Get-DbaAvailabilityGroup -SqlInstance $instance
        foreach ($ag in $ags) {
            $initialState += &#091;PSCustomObject]@{
                Instance = $instance
                AGName   = $ag.Name
                Primary  = $ag.PrimaryReplica
            }
            $output = "Instance = $instance, AGName = $($ag.Name), Primary  = $($ag.PrimaryReplica)"
            Out-Log -Type INFO "$output" 
        }
    }
    #Add configuration to a JSON file
    $initialState | ConvertTo-Json | Out-File $ConfigFile
    $output = $initialState | Format-Table
}

#Function to save Availability Group configuration in a JSON file
Function AGsRestoreConfiguration(){
param (
&#091;Array] $instances,
&#091;String] $ConfigFile
)
    if (!(Test-Path -Path $ConfigFile)) {
        Add-Error "Configuration file is missing"
        Add-Error "Exit without having restore the AG configurations"
        Return
    }

    $initialState = Get-Content $ConfigFile | ConvertFrom-Json

    foreach ($entry in $initialState) {
        write-host $entry
        $ag = Get-DbaAvailabilityGroup -SqlInstance $entry.Instance -AvailabilityGroup $entry.AGName
        if (($ag.PrimaryReplica -ne $entry.Primary) -and ($ag.LocalReplicaRole -eq "Secondary")) {
            Write-Host "Failover of Availability Group $($entry.AGName) to $($entry.Primary)"
            Out-Log -Type INFO "Failover of Availability Group $($entry.AGName) to $($entry.Primary)"
            Try {
                Invoke-DbaAgFailover -SqlInstance $entry.Instance -AvailabilityGroup $entry.AGName -Confirm:$false
            }
            Catch {
                Write-Host "Error during failover of Availability Group $($entry.AGName) to $($entry.Primary)"
                Add-Error "Error during failover of Availability Group $($entry.AGName) to $($entry.Primary)"                
            }
        }
    }
}

#Function to failover a list of instances to a specific host
Function AGsFailoverTo(){
param (
&#091;Array] $instances,
&#091;String] $TargetNode
)
    foreach ($instance in $instances) {
        $ags = Get-DbaAvailabilityGroup -SqlInstance $instance
        foreach ($ag in $ags) {
            if (($ag.ComputerName -eq $TargetNode) -and ($ag.LocalReplicaRole -eq "Secondary")) {
                Write-Host "Failover of $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                Out-Log -Type INFO "Failover of $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                Try {
                    Invoke-DbaAgFailover -SqlInstance $instance -AvailabilityGroup $ag.Name -Confirm:$false
                }
                Catch {
                    Write-Host "Error during failover of Availability Group $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                    Add-Error "Error during failover of Availability Group $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                }
            }
        }
    }
    Out-Log -Type INFO "All Availability Groups have been failover to node $TargetNode"

}

#LOG file path and name
$LogFileName = "AGsFailoverForPatching.txt"
$LogFilePath = "\\ShareFolder\LOG"
$LogFile = "$LogFilePath\$LogFileName"

#Configuration file path and name
$AGsConfigFileName = "initial_state.json"
$AGsConfigFilePath = "\\ShareFolder\LOG"
$AGsConfigFile = "$AGsConfigFilePath\$AGsConfigFileName"

###################################################################
#Failover all Availability group to a specify node
Out-Log -Type INFO "START TO FAILOVER ALL AVAILABILITY GROUPS"
#Target node
$targetNode = "server1"
Out-Log -Type INFO "Target node: $targetNode"

#Find all instances
$instances = @('server1\Instance1','server1\Instance2','server1\Instance3','server1\Instance4','server2\Instance1','server2\Instance2','server2\Instance3','server2\Instance4')
Out-Log -Type INFO "Instances available: $instances"

#Call the function AGsFailoverTo
AGsFailoverTo -instances $instances -TargetNode $targetNode</code></pre>



<ul class="wp-block-list">
<li>Schedule is the third Saturday of each month at 10PM with a duration of 2 hours</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="640" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info9_noname-1024x640.jpg" alt="" class="wp-image-40500" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info9_noname-1024x640.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info9_noname-300x188.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info9_noname-768x480.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info9_noname.jpg 1399w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<ul class="wp-block-list">
<li>There are also some possibles options that can be enabled or disabled for the job.<br>I select the Reboot Countdown to send a message to the users of the server before the reboot, a message is also sent to a distribution list at the execution start and after completion and the patches will try to be downloaded by the Qualys agent before the job schedule.</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="795" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info10_noname-1024x795.jpg" alt="" class="wp-image-40501" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info10_noname-1024x795.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info10_noname-300x233.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info10_noname-768x597.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info10_noname.jpg 1039w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="881" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info11_noname-1024x881.jpg" alt="" class="wp-image-40502" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info11_noname-1024x881.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info11_noname-300x258.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info11_noname-768x660.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/09/Qualys_Basis_Info11_noname.jpg 1064w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>It&#8217;s finish for my first job, I need to create now the second one.<br>Qualys gives the possibility to clone job, so I&#8217;m using this feacture and change the needed information for my second job.<br>This job will patch my second server and will have to:</p>



<ul class="wp-block-list">
<li>Failover all Availability Groups from the node server2 to the node server1 (already done in the first job as post-action, but I do it again)</li>



<li>Patch the instances on node server2 with the available patches</li>



<li>Reboot the server</li>



<li>Redistribute the Availability Groups based on the JSON file created in the pre-action of the first job</li>
</ul>



<p>In terms of Qualys, this job has the following information:</p>



<ul class="wp-block-list">
<li>A name to identify the job, here SQLServer_server2_M_3rd_Sunday_0001 as this job will be executed the third Sunday of each month at 00:01AM, after the completion of the first one</li>



<li>Select the asset (here the server) where the patches will be executed: server2</li>



<li>Pre-actions: it will be a PowerShell script which will:<br>o Failover the Availability Groups from node server2 to server1 with the following PowerShell script (the script is also logging in a file)</li>
</ul>



<pre class="wp-block-code"><code>###Logging functions
Function Out-Log() {
param(
&#091;ValidateSet('INFO','WARNING','ERROR')]
&#091;String] $Type = 'INFO',
&#091;String] $Message
)
    '&#091;'+(Get-Date -f 'yyyy-MM-dd HH:mm:ss') +'] ' + ' - &#091;' + $Type + '] - ' + $Message | Out-File -FilePath $LogFile -Append;
}

Function Add-Warning() {
param(
&#091;String] $Warning
)
    If ($Warning) {
    Out-Log -Type WARNING -Message $Warning;
    }
}

Function Add-Error() {
param(
&#091;String] $Message
)
    Out-Log -Type ERROR -Message $Message;
}

###AGs functions
#Function to save Availability Group configuration in a JSON file
Function AGsSaveConfiguration(){
param (
&#091;Array] $instances,
&#091;String] $ConfigFile
)
    Out-Log -Type INFO "The following configurations have been saved:"
    $initialState = @()
    foreach ($instance in $instances) {
        $ags = Get-DbaAvailabilityGroup -SqlInstance $instance
        foreach ($ag in $ags) {
            $initialState += &#091;PSCustomObject]@{
                Instance = $instance
                AGName   = $ag.Name
                Primary  = $ag.PrimaryReplica
            }
            $output = "Instance = $instance, AGName = $($ag.Name), Primary  = $($ag.PrimaryReplica)"
            Out-Log -Type INFO "$output" 
        }
    }
    #Add configuration to a JSON file
    $initialState | ConvertTo-Json | Out-File $ConfigFile
    $output = $initialState | Format-Table
}

#Function to save Availability Group configuration in a JSON file
Function AGsRestoreConfiguration(){
param (
&#091;Array] $instances,
&#091;String] $ConfigFile
)
    if (!(Test-Path -Path $ConfigFile)) {
        Add-Error "Configuration file is missing"
        Add-Error "Exit without having restore the AG configurations"
        Return
    }

    $initialState = Get-Content $ConfigFile | ConvertFrom-Json

    foreach ($entry in $initialState) {
        write-host $entry
        $ag = Get-DbaAvailabilityGroup -SqlInstance $entry.Instance -AvailabilityGroup $entry.AGName
        if (($ag.PrimaryReplica -ne $entry.Primary) -and ($ag.LocalReplicaRole -eq "Secondary")) {
            Write-Host "Failover of Availability Group $($entry.AGName) to $($entry.Primary)"
            Out-Log -Type INFO "Failover of Availability Group $($entry.AGName) to $($entry.Primary)"
            Try {
                Invoke-DbaAgFailover -SqlInstance $entry.Instance -AvailabilityGroup $entry.AGName -Confirm:$false
            }
            Catch {
                Write-Host "Error during failover of Availability Group $($entry.AGName) to $($entry.Primary)"
                Add-Error "Error during failover of Availability Group $($entry.AGName) to $($entry.Primary)"                
            }
        }
    }
}

#Function to failover a list of instances to a specific host
Function AGsFailoverTo(){
param (
&#091;Array] $instances,
&#091;String] $TargetNode
)
    foreach ($instance in $instances) {
        $ags = Get-DbaAvailabilityGroup -SqlInstance $instance
        foreach ($ag in $ags) {
            if (($ag.ComputerName -eq $TargetNode) -and ($ag.LocalReplicaRole -eq "Secondary")) {
                Write-Host "Failover of $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                Out-Log -Type INFO "Failover of $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                Try {
                    Invoke-DbaAgFailover -SqlInstance $instance -AvailabilityGroup $ag.Name -Confirm:$false
                }
                Catch {
                    Write-Host "Error during failover of Availability Group $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                    Add-Error "Error during failover of Availability Group $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                }
            }
        }
    }
    Out-Log -Type INFO "All Availability Groups have been failover to node $TargetNode"
}

#LOG file path and name
$LogFileName = "AGsFailoverForPatching.txt"
$LogFilePath = "\\ShareFolder\LOG"
$LogFile = "$LogFilePath\$LogFileName"

#Configuration file path and name
$AGsConfigFileName = "initial_state.json"
$AGsConfigFilePath = "\\ShareFolder\LOG"
$AGsConfigFile = "$AGsConfigFilePath\$AGsConfigFileName"

###################################################################
#Failover all Availability group to a specify node
Out-Log -Type INFO "START TO FAILOVER ALL AVAILABILITY GROUPS"
#Target node
$targetNode = "server1"
Out-Log -Type INFO "Target node: $targetNode"

#Find all instances
$instances = @('server1\Instance1','server1\Instance2','server1\Instance3','server1\Instance4','server2\Instance1','server2\Instance2','server2\Instance3','server2\Instance4')
Out-Log -Type INFO "Instances available: $instances"

#Call the function AGsFailoverTo
AGsFailoverTo -instances $instances -TargetNode $targetNode
</code></pre>



<ul class="wp-block-list">
<li>Select the patches to apply to the assets, here we will select then automatically based on the filter patch.appFamily: SQL Server (like for the first job)</li>



<li>Post-actions: once the instances have been patched and the server rebooted, we need to redistribute the Availability Groups over the 2 nodes based on the JSON file created at the beginning of this patch process. For that we execute the following PowerShell script:</li>
</ul>



<pre class="wp-block-code"><code>###Logging functions
Function Out-Log() {
param(
&#091;ValidateSet('INFO','WARNING','ERROR')]
&#091;String] $Type = 'INFO',
&#091;String] $Message
)
    '&#091;'+(Get-Date -f 'yyyy-MM-dd HH:mm:ss') +'] ' + ' - &#091;' + $Type + '] - ' + $Message | Out-File -FilePath $LogFile -Append;
}

Function Add-Warning() {
param(
&#091;String] $Warning
)
    If ($Warning) {
    Out-Log -Type WARNING -Message $Warning;
    }
}

Function Add-Error() {
param(
&#091;String] $Message
)
    Out-Log -Type ERROR -Message $Message;
}

###AGs functions
#Function to save Availability Group configuration in a JSON file
Function AGsSaveConfiguration(){
param (
&#091;Array] $instances,
&#091;String] $ConfigFile
)
    Out-Log -Type INFO "The following configurations have been saved:"
    $initialState = @()
    foreach ($instance in $instances) {
        $ags = Get-DbaAvailabilityGroup -SqlInstance $instance
        foreach ($ag in $ags) {
            $initialState += &#091;PSCustomObject]@{
                Instance = $instance
                AGName   = $ag.Name
                Primary  = $ag.PrimaryReplica
            }
            $output = "Instance = $instance, AGName = $($ag.Name), Primary  = $($ag.PrimaryReplica)"
            Out-Log -Type INFO "$output" 
        }
    }
    #Add configuration to a JSON file
    $initialState | ConvertTo-Json | Out-File $ConfigFile
    $output = $initialState | Format-Table
}

#Function to save Availability Group configuration in a JSON file
Function AGsRestoreConfiguration(){
param (
&#091;Array] $instances,
&#091;String] $ConfigFile
)
    if (!(Test-Path -Path $ConfigFile)) {
        Add-Error "Configuration file is missing"
        Add-Error "Exit without having restore the AG configurations"
        Return
    }

    $initialState = Get-Content $ConfigFile | ConvertFrom-Json

    foreach ($entry in $initialState) {
        write-host $entry
        $ag = Get-DbaAvailabilityGroup -SqlInstance $entry.Instance -AvailabilityGroup $entry.AGName
        if (($ag.PrimaryReplica -ne $entry.Primary) -and ($ag.LocalReplicaRole -eq "Secondary")) {
            Write-Host "Failover of Availability Group $($entry.AGName) to $($entry.Primary)"
            Out-Log -Type INFO "Failover of Availability Group $($entry.AGName) to $($entry.Primary)"
            Try {
                Invoke-DbaAgFailover -SqlInstance $entry.Instance -AvailabilityGroup $entry.AGName -Confirm:$false
            }
            Catch {
                Write-Host "Error during failover of Availability Group $($entry.AGName) to $($entry.Primary)"
                Add-Error "Error during failover of Availability Group $($entry.AGName) to $($entry.Primary)"                
            }
        }
    }
}

#Function to failover a list of instances to a specific host
Function AGsFailoverTo(){
param (
&#091;Array] $instances,
&#091;String] $TargetNode
)
    foreach ($instance in $instances) {
        $ags = Get-DbaAvailabilityGroup -SqlInstance $instance
        foreach ($ag in $ags) {
            if (($ag.ComputerName -eq $TargetNode) -and ($ag.LocalReplicaRole -eq "Secondary")) {
                Write-Host "Failover of $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                Out-Log -Type INFO "Failover of $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                Try {
                    Invoke-DbaAgFailover -SqlInstance $instance -AvailabilityGroup $ag.Name -Confirm:$false
                }
                Catch {
                    Write-Host "Error during failover of Availability Group $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                    Add-Error "Error during failover of Availability Group $($ag.Name) to $TargetNode from $($ag.PrimaryReplica)"
                }
            }
        }
    }
    Out-Log -Type INFO "All Availability Groups have been failover to node $TargetNode"

}

#LOG file path and name
$LogFileName = "AGsFailoverForPatching.txt"
$LogFilePath = "\\ShareFolderLOG"
$LogFile = "$LogFilePath\$LogFileName"

#Configuration file path and name
$AGsConfigFileName = "initial_state.json"
$AGsConfigFilePath = "\\ShareFolder\LOG"
$AGsConfigFile = "$AGsConfigFilePath\$AGsConfigFileName"

###################################################################
#Restore Availability Group configuration for all instances
Out-Log -Type INFO "START TO RESTORE AVAILABILITY GROUPS CONFIGURATION"

#Call the function AGsRestoreConfiguration
$instances = @('server1\Instance1','server1\Instance2','server1\Instance3','server1\Instance4','server2\Instance1','server2\Instance2','server2\Instance3','server2\Instance4')
AGsRestoreConfiguration -instances $instances -ConfigFile $AGsConfigFile

Out-Log -Type INFO "Availability Group configuratons restored successfully"
Out-Log -Type INFO "******************** END PROCESS ********************"
</code></pre>



<ul class="wp-block-list">
<li>Schedule is the third Sunday of each month at 00:01AM with a duration of 2 hours. With this schedule this job will be executed after the end of the first one. In Qualys there is for the moment no possibility to start a job when another one is completed…</li>



<li>For options, I select the Reboot Countdown to send a message to the users of the server before the reboot, a message is also sent to a distribution list at the execution start and after completion to admin and the patches will try to be downloaded by the Qualys agent before the job schedule.</li>
</ul>



<p>Both jobs are now created. After their execution the two Always On cluster nodes will have been patched and the Availability Groups redistributed as it was before to start.</p>



<p>Qualys Patch Management is relatively easy to use. All patches based on your selection are availables and can be applied easily.<br>The pre and post actions give the possibility to prepare the patching: here fail-over, save the inital configuration, redistribute the AGs at the end.<br>It was a good experience and I hope it can help <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br> </p>
<p>L’article <a href="https://www.dbi-services.com/blog/how-to-patch-sql-server-instances-with-qualys-patch-management/">How to patch SQL Server instances with Qualys Patch Management</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/how-to-patch-sql-server-instances-with-qualys-patch-management/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Contained Availability Group and login failed</title>
		<link>https://www.dbi-services.com/blog/contained-availability-group-and-login-failed/</link>
					<comments>https://www.dbi-services.com/blog/contained-availability-group-and-login-failed/#respond</comments>
		
		<dc:creator><![CDATA[Stéphane Savorgnano]]></dc:creator>
		<pubDate>Fri, 04 Jul 2025 09:22:50 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Database management]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Contained Availability Groups]]></category>
		<category><![CDATA[dbatools]]></category>
		<category><![CDATA[login failed]]></category>
		<category><![CDATA[permissions]]></category>
		<category><![CDATA[SQL Server 2012]]></category>
		<category><![CDATA[SQL Server 2022]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=39361</guid>

					<description><![CDATA[<p>A contained availability group is a special availability group which manages its owns logins, users, permissions, jobs&#8230; with a dedicated master and msdb system databases, more information here.At a customer place we found some unexpected &#8220;login failed&#8221; for the group managed service account in the SQL Server logs. The source of the issue is coming [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/contained-availability-group-and-login-failed/">Contained Availability Group and login failed</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>A contained availability group is a special availability group which manages its owns logins, users, permissions, jobs&#8230; with a dedicated master and msdb system databases, more information <a href="https://learn.microsoft.com/en-us/sql/database-engine/availability-groups/windows/contained-availability-groups-overview?view=sql-server-ver17">here</a>.<br>At a customer place we found some unexpected &#8220;login failed&#8221; for the group managed service account in the SQL Server logs.</p>



<p>The source of the issue is coming from a PowerShell script executing some dbatools cmdlets. This script is executed inside an agent job so by the SQL Server service account which is sysadmin of all instances.<br>I will reproduce the issue on my lab environment.</p>



<p>This script sniffs instances for a multiple instance server with the cmdlet Find-DbaInstance, it returns a list of instances:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="278" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-3-1024x278.png" alt="" class="wp-image-39370" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-3-1024x278.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-3-300x81.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-3-768x208.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-3.png 1331w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>The instance list received will then be used to gather some information, for example with the cmdlet Get-DbaDatabase:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="455" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-4-1024x455.png" alt="" class="wp-image-39371" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-4-1024x455.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-4-300x133.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-4-768x341.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-4-1536x683.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-4.png 1674w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>The login failed is visible here but, of course, when it&#8217;s part of a job you don&#8217;t have it so obviously but you can see it on the SQL Server logs or in the logs of the job:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="120" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-5-1024x120.png" alt="" class="wp-image-39372" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-5-1024x120.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-5-300x35.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-5-768x90.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-5-1536x180.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/image-5.png 1704w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>The question is more why do we have a failed login for the service account which is sysadmin of all instances.<br>As we have SQL Server 2019 and SQL Server 2022 with Always Availability Group on this server, we put in place contained availability group for our SQL Server 2022 instances.<br>A the creation of the contained availability groups we used an active directory service account which was sysadmin of all instances which has been replaced a bit later by a group managed service account. This new gmsa is also sysadmin of all instances.<br>The problem is coming from the creation of the contained availability group and how this contained AG manages logins, users&#8230;<br>Logins part of the instance are local to the instance and won&#8217;t be copied to the system database of the contained AG when you create it. What does it means? It means that if you have created a database with logins and users before the creation of the contained AG, when you move your database in the contained AG, logins and users won&#8217;t be part of this contained AG. Users won&#8217;t be able to connect to the database until you connect in the context of your contained AG through the listener and recreate those logins.<br>There is one exception, all logins mapped with the sysadmin server role will be copied automatically to the master system database of a created contained AG. <br>It was the case of our old service account which has been copied to all our contained AG, but when we changed it to a new group managed service account, this new login, even with sysadmin server role has not been copied automatically to the master database of the contained AG. This conduct to our failed login when the gmsa tries to connect to databases part of a contained AG.</p>



<p>This problem seems to be evident but contained AG has been introduced with SQL Server 2022 while AwaysOn Availability Groups with SQL Server 2012. As DBA, we took some habits with availability groups which cannot be reproduced with contained AG. So take care if you or one of your customer use them to change your way of thinking and remember the contained AG specificities <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><br></p>
<p>L’article <a href="https://www.dbi-services.com/blog/contained-availability-group-and-login-failed/">Contained Availability Group and login failed</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/contained-availability-group-and-login-failed/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL Server 2025 &#8211; Backups to secondary replicas of an availability group</title>
		<link>https://www.dbi-services.com/blog/sql-server-2025-backups-to-secondary-replicas-of-an-availability-group/</link>
					<comments>https://www.dbi-services.com/blog/sql-server-2025-backups-to-secondary-replicas-of-an-availability-group/#respond</comments>
		
		<dc:creator><![CDATA[Stéphane Savorgnano]]></dc:creator>
		<pubDate>Fri, 23 May 2025 14:28:10 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Availability group backups]]></category>
		<category><![CDATA[Backup]]></category>
		<category><![CDATA[Diff backups]]></category>
		<category><![CDATA[Full backups]]></category>
		<category><![CDATA[SQL Server 2025]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=38908</guid>

					<description><![CDATA[<p>Backup operations can increase significantly I/O and even CPU utilization when we use backup compression. To avoid using resources on primary replica, offloading backups to a synchronized or synchronizing replicas seems to be the good solution even if we have some limitations.For the time being, supported backups on a secondary replicas are: So we cannot [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/sql-server-2025-backups-to-secondary-replicas-of-an-availability-group/">SQL Server 2025 &#8211; Backups to secondary replicas of an availability group</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Backup operations can increase significantly I/O and even CPU utilization when we use backup compression. To avoid using resources on primary replica, offloading backups to a synchronized or synchronizing replicas seems to be the good solution even if we have some limitations.<br>For the time being, supported backups on a secondary replicas are:</p>



<ul class="wp-block-list">
<li>Full backup with copy_only</li>



<li>Transaction log backup</li>
</ul>



<p>So we cannot perform:</p>



<ul class="wp-block-list">
<li>Normal Full backup</li>



<li>Differential backup</li>



<li>Transaction Log backup with copy_only</li>
</ul>



<p>Those limitations may cause some problems during point-in-time restore as with copy_only backup point-in-time restore is not possible. This can lead to point-in-time restore taking lots of time because the last Full backup without copy-only is far away on time and we need to apply thousands of transaction log backups or in worst inability to execute point-in-time restore&#8230;</p>



<p>With <a href="https://www.dbi-services.com/blog/sql-server-2025-public-preview-and-ssms-21-now-available/">SQL Server 2025</a>, not just Full backups with copy_only are available on secondary replicas, it introduces normal Full backups as well as Differential backups.<br>To perform those new backups on secondary replicas we need to enable specific trace flags:</p>



<ul class="wp-block-list">
<li>Trace flag 3261 will enable differential backups on secondary replicas</li>



<li>Trace flag 3262 will enable full backups on secondary replicas</li>
</ul>



<p>First of all we need to install the new <a href="https://learn.microsoft.com/en-us/ssms/release-notes-21">SQL Server Management Studio 21</a>, two SQL Server 2025 CTP2 instances and configure an Always On Availability Group with backup Preferences set to Prefer Secondary.</p>



<p>Before to try the new Full and Differential backups on a secondary replica with the trace flags, lets try to run a Full backup without copy_only on a secondary:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1486" height="518" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_1.jpg" alt="" class="wp-image-38920" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_1.jpg 1486w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_1-300x105.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_1-1024x357.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_1-768x268.jpg 768w" sizes="auto, (max-width: 1486px) 100vw, 1486px" /></figure>



<p>As expected it fails.</p>



<p>Lets try to configure those new backups and enable the new trace flags, here globally, but we will do it with -T in the startup parameters of the SQL Service to persist them later on:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="972" height="813" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_2.jpg" alt="" class="wp-image-38921" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_2.jpg 972w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_2-300x251.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_2-768x642.jpg 768w" sizes="auto, (max-width: 972px) 100vw, 972px" /></figure>



<p>Once done on my two cluster nodes, I will try to execute a Full backup of the AdventureWorks database on a secondary replica:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="482" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_3-1024x482.jpg" alt="" class="wp-image-38922" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_3-1024x482.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_3-300x141.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_3-768x362.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_3.jpg 1423w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>My Full backup succeeds and it&#8217;s not a Full backup with copy_only but a normal one:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="997" height="371" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_4.jpg" alt="" class="wp-image-38923" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_4.jpg 997w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_4-300x112.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_4-768x286.jpg 768w" sizes="auto, (max-width: 997px) 100vw, 997px" /></figure>



<p>We can also execute a Differential backup and as we can see my Incremental backup has been executed successfully:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="380" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_5-1024x380.jpg" alt="" class="wp-image-38924" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_5-1024x380.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_5-300x111.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_5-768x285.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_5.jpg 1417w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="362" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_6-1024x362.jpg" alt="" class="wp-image-38925" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_6-1024x362.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_6-300x106.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_6-768x271.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/SQL_Server_2025_Backup_6.jpg 1116w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Removing the backup workload from the primary replica and relocate it on the secondary is a great benefit. Before SQL Server 2025, this configuration was not robust as it should be due to the copy_only restriction and the inability to execute Differential backup.<br>With this new version no doubt that more and more customers will move to this configuration. Of course backup jobs need to be tuned to just execute the backup when the database is on its preferred backup replica but most of the backup jobs already check that <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>L’article <a href="https://www.dbi-services.com/blog/sql-server-2025-backups-to-secondary-replicas-of-an-availability-group/">SQL Server 2025 &#8211; Backups to secondary replicas of an availability group</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/sql-server-2025-backups-to-secondary-replicas-of-an-availability-group/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Starting with PowerShell 7 and parallelization</title>
		<link>https://www.dbi-services.com/blog/starting-with-powershell-7-and-parallelization/</link>
					<comments>https://www.dbi-services.com/blog/starting-with-powershell-7-and-parallelization/#respond</comments>
		
		<dc:creator><![CDATA[Stéphane Savorgnano]]></dc:creator>
		<pubDate>Wed, 26 Feb 2025 08:49:14 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Database management]]></category>
		<category><![CDATA[Development & Performance]]></category>
		<category><![CDATA[MS Teams]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Parallelization]]></category>
		<category><![CDATA[PowerShell]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=36946</guid>

					<description><![CDATA[<p>For the time being Windows PowerShell 5.1 is installed with Windows Server. It means that if you want to use or even test PowerShell 7 you need to install it by your own.To be honest, even if I&#8217;m using PowerShell as a DBA more or less every day, I did not take too much care [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/starting-with-powershell-7-and-parallelization/">Starting with PowerShell 7 and parallelization</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>For the time being Windows PowerShell 5.1 is installed with Windows Server. It means that if you want to use or even test PowerShell 7 you need to install it by your own.<br>To be honest, even if I&#8217;m using PowerShell as a DBA more or less every day, I did not take too much care of PowerShell 7 until I used it at a customer place with a new parallelization functionality that we will discuss later on.</p>



<h2 class="wp-block-heading" id="h-introduction">Introduction</h2>



<p>For reminder, PowerShell 7 is an open-source and cross-platform edition of PowerShell. It means that it can be used on Windows platforms but also on MacOS or Linux.<br>The good point is that you can install it without removing Windows PowerShell 5.1. Both version can cohabit because of:</p>



<ul class="wp-block-list">
<li>Separate installation path and executable name
<ul class="wp-block-list">
<li>path with 5.1 like $env:WINDIR\System32\WindowsPowerShell\v1.0</li>



<li>path with 7 like $env:ProgramFiles\PowerShell\7</li>



<li>executable with PowerShell 5.1 is powershell.exe and with PowerShell 7 is pwsh.exe</li>
</ul>
</li>



<li>Separate PSModulePath</li>



<li>Separate profiles for each version
<ul class="wp-block-list">
<li>path with 5.1 is $HOME\Documents\WindowsPowerShell</li>



<li>path with 7 is $HOME\Documents\PowerShell</li>
</ul>
</li>



<li>Improved module compatibility</li>



<li>New remoting endpoints</li>



<li>Group policy support</li>



<li>Separate Event logs</li>
</ul>



<h2 class="wp-block-heading" id="h-installation">Installation</h2>



<p>To install PowerShell 7 on Windows Servers the easiest way is to use a MSI package. The last version to download is the<a href="https://github.com/PowerShell/PowerShell/releases/download/v7.5.0/PowerShell-7.5.0-win-x64.msi"> 7.5.0</a>. Once downloaded, double click the msi file and follow the installation:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="491" height="382" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install1.jpg" alt="" class="wp-image-36954" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install1.jpg 491w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install1-300x233.jpg 300w" sizes="auto, (max-width: 491px) 100vw, 491px" /></figure>



<p>By default and as mentioned previously, PowerShell 7 will be installed on C:\Program Files\PowerShell\ :</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="489" height="385" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install2.jpg" alt="" class="wp-image-36957" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install2.jpg 489w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install2-300x236.jpg 300w" sizes="auto, (max-width: 489px) 100vw, 489px" /></figure>



<p>Some customization are possible, we will keep the default selections:<br><br></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="492" height="383" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install3.jpg" alt="" class="wp-image-36959" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install3.jpg 492w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install3-300x234.jpg 300w" sizes="auto, (max-width: 492px) 100vw, 492px" /></figure>



<p>Starting with PowerShell 7.2, it is possible to update PowerShell 7 with traditional Microsoft Update:<br></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="490" height="386" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install4.jpg" alt="" class="wp-image-36961" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install4.jpg 490w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install4-300x236.jpg 300w" sizes="auto, (max-width: 490px) 100vw, 490px" /></figure>



<p>After some seconds installation is done:<br></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="489" height="385" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install7.jpg" alt="" class="wp-image-36965" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install7.jpg 489w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install7-300x236.jpg 300w" sizes="auto, (max-width: 489px) 100vw, 489px" /></figure>



<p>We can start the PowerShell 7 with the cmd pwsh.exe. As we can see below both versions coexist on my Windows Server:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="686" height="619" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install8.jpg" alt="" class="wp-image-36966" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install8.jpg 686w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/01/PS7_install8-300x271.jpg 300w" sizes="auto, (max-width: 686px) 100vw, 686px" /></figure>



<h2 class="wp-block-heading" id="h-new-features">New features</h2>



<p>PowerShell 7 introduces some interesting new features compare to PowerShell 5.</p>



<ul class="wp-block-list">
<li><a href="https://learn.microsoft.com/en-us/previous-versions/powershell/scripting/whats-new/what-s-new-in-powershell-70?view=powershell-7.3#parallel-execution-added-to-foreach-object">ForEach-Object with parallel execution</a><br>Execute the script block in parallel for each object. A parameter ThrottleLimit limits the number of script blocks running at the same time, default value is 5.<br>Here we search the instance properties by server and limit the parallelization to 2 server at the same time.<br>$computers = &#8216;thor90&#8242;,&#8217;thor91&#8242;,&#8217;thor10&#8242;,&#8217;thor11&#8217;<br>$InstanceProperties = $computers | ForEach-Object -Parallel {<br>$instances = (Find-DbaInstance -ComputerName $_).SqlInstance;<br>Get-DbaInstanceProperty -SqlInstance $instances<br>} -ThrottleLimit 2<br></li>



<li><a href="https://learn.microsoft.com/en-us/previous-versions/powershell/scripting/whats-new/what-s-new-in-powershell-70?view=powershell-7.3#ternary-operator">Ternary operator</a><br>A simplified if-else statement with &lt;condition&gt; ? &lt;condition true&gt; : &lt;condition false&gt;<br></li>



<li><a href="https://learn.microsoft.com/en-us/previous-versions/powershell/scripting/whats-new/what-s-new-in-powershell-70?view=powershell-7.3#pipeline-chain-operators">Pipeline chain operators</a><br>The &amp;&amp; operator executes the right-hand pipeline, if the left-hand pipeline succeeded. Reverse, the || operator executes the right-hand pipeline if the left-hand pipeline failed.<br></li>



<li><a href="https://learn.microsoft.com/en-us/previous-versions/powershell/scripting/whats-new/what-s-new-in-powershell-70?view=powershell-7.3#null-coalescing-assignment-and-conditional-operators">coalescence, assignment and conditional operators</a><br>PowerShell 7 includes Null coalescing operator ??, Null conditional assignment ??=, and Null conditional member access operators ?. and ?[]<br></li>



<li><a href="https://learn.microsoft.com/en-us/previous-versions/powershell/scripting/whats-new/what-s-new-in-powershell-70?view=powershell-7.3#new-view-conciseview-and-cmdlet-get-error">New management of error message and new cmdlet Get-Error</a><br>This new cmdlet Get-Error displays the full detailed of the last error with inner exceptions.<br>A parameter Newest allows to select the number of error you would like to display</li>
</ul>



<p>On this blog, post I would to concentrate to the parallelization with the ForEach-Object -Parallel</p>



<h2 class="wp-block-heading" id="h-powershell-7-parallelization">PowerShell 7 parallelization</h2>



<p>This new feature comes with the know ForEach-Object cmdlet which performs an operation on each item in a collection of input objects.<br>Starting with PowerShell 7.0 a new parameter set, called &#8220;Parallel&#8221;, gives the possibility to run each script block in parallel instead of sequentially. The &#8220;ThrottleLimit&#8221; parameter, if used, limits the number of script blocks which will run at the same time, if it is not specified the default value is 5.</p>



<p> ForEach-Object -Parallel &lt;scriptblock&gt; -ThrottleLimit</p>



<p>We can test this new feature with a small example.<br>If we execute the following script as before, the script block is executed sequentially:</p>



<pre class="wp-block-code"><code>PS C:\Users\administrator.ADSTS&gt; 1..16 | ForEach-Object { Get-Date; sleep 10 }

Friday, February 14, 2025 9:00:02 AM
Friday, February 14, 2025 9:00:12 AM
Friday, February 14, 2025 9:00:22 AM
Friday, February 14, 2025 9:00:32 AM
Friday, February 14, 2025 9:00:42 AM
Friday, February 14, 2025 9:00:52 AM
Friday, February 14, 2025 9:01:02 AM
Friday, February 14, 2025 9:01:12 AM
Friday, February 14, 2025 9:01:22 AM
Friday, February 14, 2025 9:01:32 AM
Friday, February 14, 2025 9:01:42 AM
Friday, February 14, 2025 9:01:52 AM
Friday, February 14, 2025 9:02:02 AM
Friday, February 14, 2025 9:02:12 AM
Friday, February 14, 2025 9:02:22 AM
Friday, February 14, 2025 9:02:32 AM</code></pre>



<p>Each line as 10 seconds more than the previous one.<br>But, if we execute this script with the new parameter Parallel and use a throttle limit of 4 we have:</p>



<pre class="wp-block-code"><code>PS C:\Users\administrator.ADSTS&gt; 1..16 | ForEach-Object -Parallel { Get-Date; sleep 10 } -ThrottleLimit 4

Friday, February 14, 2025 8:59:01 AM
Friday, February 14, 2025 8:59:01 AM
Friday, February 14, 2025 8:59:01 AM
Friday, February 14, 2025 8:59:01 AM
Friday, February 14, 2025 8:59:11 AM
Friday, February 14, 2025 8:59:11 AM
Friday, February 14, 2025 8:59:11 AM
Friday, February 14, 2025 8:59:11 AM
Friday, February 14, 2025 8:59:21 AM
Friday, February 14, 2025 8:59:21 AM
Friday, February 14, 2025 8:59:21 AM
Friday, February 14, 2025 8:59:21 AM
Friday, February 14, 2025 8:59:31 AM
Friday, February 14, 2025 8:59:31 AM
Friday, February 14, 2025 8:59:31 AM
Friday, February 14, 2025 8:59:31 AM</code></pre>



<p>Here we have 4 groups of 4 lines with the same time as we executed the script block in parallel with limitation of the parallelization to 4.<br>Of course, the different commands included in the script block are executed sequentially.</p>



<p>This feature uses the PowerShell runspaces to execute script blocks in parallel.<br>Variables can be passed into the script block with the $using: keyword, the only variable automatically passed is the pipe object.<br>Each runspace will execute a script block in a thread, so the ThrottleLimit parameter needs to be set according to the number of core of the server where you are running. If you VM has 2 cores, it makes no sense to put the limit to 4&#8230;</p>



<p>This new script will execute a maintenance job on different instances of the same server, passing the job name in the block script with the $using: keyword:</p>



<pre class="wp-block-code"><code>PS C:\Users\administrator.ADSTS&gt; $ThrottleLimit = 2
PS C:\Users\administrator.ADSTS&gt; $JobName = 'DBI_MAINTENANCE_MAINTENANCE_USER_DATABASES'
PS C:\Users\administrator.ADSTS&gt; $computers = 'thor90'
PS C:\Users\administrator.ADSTS&gt; $SqlInstances = Find-DbaInstance -ComputerName $computers -EnableException
PS C:\Users\administrator.ADSTS&gt; $SqlInstances

ComputerName InstanceName SqlInstance    Port  Availability Confidence ScanTypes
------------ ------------ -----------    ----  ------------ ---------- ---------
thor90       CMS          thor90\CMS     50074 Available    High       Default
thor90       SQL16_1      thor90\SQL16_1 62919 Available    High       Default
thor90       SQL19_1      thor90\SQL19_1 1433  Available    High       Default
thor90       SQL22_1      thor90\SQL22_1 50210 Available    High       Default
thor90       MSSQLSERVER  thor90         1434  Available    High       Default

PS C:\Users\administrator.ADSTS&gt; $SqlInstances | ForEach-Object -Parallel {
&gt;&gt;     $Out = "Starting Job on $using:JobName &#091;" + $_.SqlInstance + "]"
&gt;&gt;     Write-Host $Out
&gt;&gt;     $res = Start-DbaAgentJob -SqlInstance $_.SqlInstance -Job $using:JobName -Wait
&gt;&gt; } -ThrottleLimit $ThrottleLimit
Starting Job on DBI_MAINTENANCE_MAINTENANCE_USER_DATABASES &#091;thor90\CMS]
Starting Job on DBI_MAINTENANCE_MAINTENANCE_USER_DATABASES &#091;thor90\SQL16_1]
Starting Job on DBI_MAINTENANCE_MAINTENANCE_USER_DATABASES &#091;thor90\SQL19_1]
Starting Job on DBI_MAINTENANCE_MAINTENANCE_USER_DATABASES &#091;thor90\SQL22_1]
Starting Job on DBI_MAINTENANCE_MAINTENANCE_USER_DATABASES &#091;thor90]
PS C:\Users\administrator.ADSTS&gt;</code></pre>



<p>We use this kind of script at a customer place to execute SQL Server Agent Jobs in parallel on instances of a big physical servers with more than 10 instances.</p>



<h2 class="wp-block-heading" id="h-conclusion">Conclusion</h2>



<p>This PowerShell 7 parallelization feature can improve performance in lots of different scenarios. But test it and don&#8217;t think that because of parallelization all your scripts will be executed quickly as running a script in parallel adds some overhead which will decrease execution of trivial script.</p>



<p></p>
<p>L’article <a href="https://www.dbi-services.com/blog/starting-with-powershell-7-and-parallelization/">Starting with PowerShell 7 and parallelization</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/starting-with-powershell-7-and-parallelization/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Boost dynamic SQL with Plan Guides</title>
		<link>https://www.dbi-services.com/blog/boost-dynamic-sql-with-plan-guide/</link>
					<comments>https://www.dbi-services.com/blog/boost-dynamic-sql-with-plan-guide/#comments</comments>
		
		<dc:creator><![CDATA[Stéphane Savorgnano]]></dc:creator>
		<pubDate>Mon, 20 Nov 2023 14:20:53 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Database management]]></category>
		<category><![CDATA[Development & Performance]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Boost performance]]></category>
		<category><![CDATA[optimize for unknown]]></category>
		<category><![CDATA[plan]]></category>
		<category><![CDATA[Plan Guides]]></category>
		<category><![CDATA[query store]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=29418</guid>

					<description><![CDATA[<p>Recently a customer asked me to check why a load process from a banking application has so poor performance.In fact this process is extremely slow, between 40 minutes up to more than one hour to load around 240&#8217;000 rows. As the application is a black box for my customer I have first to find out [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/boost-dynamic-sql-with-plan-guide/">Boost dynamic SQL with Plan Guides</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Recently a customer asked me to check why a load process from a banking application has so poor performance.<br>In fact this process is extremely slow, between 40 minutes up to more than one hour to load around 240&#8217;000 rows. As the application is a black box for my customer I have first to find out which queries are running during this process, which ones are the most time consuming and why.</p>



<p>To find out which queries are running against your instance or a specific database you have multiples solutions going from the &#8220;old school&#8221;  SQL Server Profiler to a more recent one the Extended Event sessions and even the Query Store which can help. In my case the Query Store was not enable, my customer is running SQL Server 2016 Enterprise Edition, so I started it.<br>I created an Extended Event session where I selected the rpc_completed event and started it during the load process.<br>I quickly found out the guilty query which was a classical parameterized Dynamic SQL executed with sp_executesql.</p>



<p>The script is looking as follow:</p>



<pre class="wp-block-code"><code>exec sp_executesql N'SELECT ...... FROM ..... WHERE ..... GROUP BY ..... ORDER BY .....' ,N'@P0 date,@P1 date,@P2 date','2023-10-20','1900-01-01','2023-10-27'</code></pre>



<p>When I ran it on my Management studio with the same parameters the duration was around 35 minutes to retrieve 6200 rows (another query runs after this one to load the 240&#8217;000 based on those ones but this query is fast enough):</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="441" data-id="29420" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_1-1024x441.jpg" alt="" class="wp-image-29420" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_1-1024x441.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_1-300x129.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_1-768x331.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_1-1536x662.jpg 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_1-2048x882.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</figure>



<p>If I check in my Query Store for the Top Resource Consumers for my database, I can find my query:</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-2 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="360" data-id="29422" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_2-1024x360.jpg" alt="" class="wp-image-29422" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_2-1024x360.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_2-300x105.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_2-768x270.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_2-1536x540.jpg 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_2-2048x720.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</figure>



<p>Now the question is: &#8220;How can I boost this query?&#8221;.<br>By chance I followed last year a SQL Bits session from <a href="https://erikdarling.com/">Erik Darling</a> about Performance Tuning and Parameter Sniffing.<br>During its session Erik spoke about the possibility to use the hint OPTIMIZE FOR.<br>Here as I have three parameters I will use the hint OPTIMIZE FOR UNKNOWN which will tell SQL Server to make a blind assumption to completely kill parameter sniffing for my three parameters.<br>My new script will look like:</p>



<pre class="wp-block-code"><code><code>exec sp_executesql N'SELECT ...... FROM ..... WHERE ..... GROUP BY ..... ORDER BY ..... <strong>OPTION (OPTIMIZE FOR UNKNOWN)</strong>' ,N'@P0 date,@P1 date,@P2 date','2023-10-20','1900-01-01','2023-10-27'</code></code></pre>



<p>When I execute my script now, the magic happens, the time to execute the query is now less than 5 seconds:</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-3 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="367" data-id="29426" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_3-1024x367.jpg" alt="" class="wp-image-29426" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_3-1024x367.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_3-300x108.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_3-768x275.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_3-1536x550.jpg 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_3-2048x734.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</figure>



<p>The tricky part is that the query is generated automatically by the application which is not developed in house, so how can I add my hint to the query?<br>With SQL Server 2022 and the possibility to add <a href="https://learn.microsoft.com/en-us/sql/relational-databases/performance/query-store-hints">hints via the Query Store</a> the task would have been easy but with SQL Server 2016 it&#8217;s not an option.</p>



<p>The solution here is the Plan Guides. <br>Plan guides give the possibility to optimize the performance of queries when there is no possibility to directly change the text of the actual query in SQL Server. Plan Guides force the optimization of queries by adding query hints or a fixed query plan to them. They can be used when queries coming from applications provided by a third-party vendor are not performing as expected. <br>In the Plan Guide, you need to mention the T-SQL statement that you need to optimize and add an OPTION clause where you define the query hints you want to use or a specific query plan you want to use to optimize the query. During execution of the query, SQL Server will match the T-SQL statement of the query to the Plan Guide and will attach the OPTION clause to the query at run time or will use the specified query plan.</p>



<p>When we create the Plan Guide, the used T-SQL must exactly match the one provided by the third party application. This query has to be the same as the SQL Server compiler receives. Microsoft advises to use the SQL Server Profiler to capture the actual batch and parameter text. If there are some difference the Plan Guide won&#8217;t be used.<br>Before to know this point, I tried to use the T-SQL text coming from the Query Store or coming from the DMVs sys.dm_exec_sql_text but created Plan Guides were never used during the execution of the query from the application. To avoid to waste time please follow this <a href="https://learn.microsoft.com/en-us/sql/relational-databases/performance/use-sql-server-profiler-to-create-and-test-plan-guides?view=sql-server-ver16">Microsoft guide</a>.</p>



<p>Once you have your T-SQL statement you can create your Plan Guide with the <a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-create-plan-guide-transact-sql?view=sql-server-ver16">sp_create_plan_guide</a> Stored Procedure.<br>My one looks like that:</p>



<pre class="wp-block-code"><code>EXEC sp_create_plan_guide 
@name = N'&lt;database_name&gt; plan guide with hint SQL coming from profiler', 
@stmt = N'the T-SQL catches with my SQL Server Profiler',
@type = N'SQL',
@params = N'@P0 date,@P1 date,@P2 date',
@hints = N'OPTION (OPTIMIZE FOR UNKNOWN)'
GO</code></pre>



<p>Once created you can see your new Plan Guides on SSMS:</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-4 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="425" height="398" data-id="29463" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_4.jpg" alt="" class="wp-image-29463" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_4.jpg 425w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_4-300x281.jpg 300w" sizes="auto, (max-width: 425px) 100vw, 425px" /></figure>
</figure>



<p>When the application will execute again the T-SQL, the Plan Guide will be used and the hint will boost the execution as expected:</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-5 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="397" data-id="29499" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_5-2-1024x397.jpg" alt="" class="wp-image-29499" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_5-2-1024x397.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_5-2-300x116.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_5-2-768x298.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_5-2-1536x595.jpg 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2023/11/PlanGuide_5-2-2048x793.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</figure>



<p>We can see that the script duration is much faster with around 5 second instead of 30 minutes and the plan uses the new Plan Guide with the hint we passed.</p>



<p>Plan Guides are very powerful and can help to provide better performance for scripts where direct modifications are not an option. A second option would be to migrate to SQL Server 2022, we are also looking on this direction at my customer place but as it will take some times, Plan Guides will be used in the meantime <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <br></p>
<p>L’article <a href="https://www.dbi-services.com/blog/boost-dynamic-sql-with-plan-guide/">Boost dynamic SQL with Plan Guides</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/boost-dynamic-sql-with-plan-guide/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Azure SQL Managed Instance link feature overview</title>
		<link>https://www.dbi-services.com/blog/azure-sql-managed-instance-link-feature-overview/</link>
					<comments>https://www.dbi-services.com/blog/azure-sql-managed-instance-link-feature-overview/#respond</comments>
		
		<dc:creator><![CDATA[Stéphane Savorgnano]]></dc:creator>
		<pubDate>Thu, 24 Nov 2022 12:30:15 +0000</pubDate>
				<category><![CDATA[Azure]]></category>
		<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[hybrid]]></category>
		<category><![CDATA[Managed Instance Link]]></category>
		<category><![CDATA[managed instances]]></category>
		<category><![CDATA[migration]]></category>
		<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=20544</guid>

					<description><![CDATA[<p>In one of my last blog-posts I spoke about Striim which is a data replication platform that can be used for migrations but also for offloaded reporting or even real-time analytics.A new Azure feature, currently on preview, named Link feature for Azure SQL Managed Instance is available. It gives the possibility to connect a SQL [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/azure-sql-managed-instance-link-feature-overview/">Azure SQL Managed Instance link feature overview</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In one of my last blog-posts I spoke about <a href="https://www.striim.com/product/">Striim</a> which is a data replication platform that can be used for migrations but also for offloaded reporting or even real-time analytics.<br>A new Azure feature, currently on preview, named Link feature for Azure SQL Managed Instance is available. It gives the possibility to connect a SQL Server hosted on-premise or in the cloud to a SQL Managed instance.<br>This feature keeps the replicate up-to-date with near real-time data replication to the cloud.<br>It gives the opportunity to have a read-only secondary in the cloud to offload the workload and take advantages of the Azure environment like built-in security, scalability. performance…</p>



<p>This new feature is based on Distributed Availability Groups. There is no prerequisite to already have an Availability Group or multiple nodes, a single node is working but multiple ones with multiples AGs are also working.</p>



<p>The link can be kept ever or remove after a transition period for a migration with near to zero downtime.</p>



<p>Before to configure a Manage Instance Link as I&#8217;m working with SQL Server 2019 we need:</p>



<ul class="wp-block-list">
<li>SQL Server 2019 Enterprise or Developer Edition with CU15 or above</li>



<li>An Azure SQL Manage Instance</li>
</ul>



<p>And also prepare our SQL Server instance with some prerequisites: </p>



<ul class="wp-block-list">
<li>Check CU15 <img loading="lazy" decoding="async" width="1555" height="246" class="wp-image-20593" style="width: 1200px" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_1-1.jpg" alt="" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_1-1.jpg 1555w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_1-1-300x47.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_1-1-1024x162.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_1-1-768x121.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_1-1-1536x243.jpg 1536w" sizes="auto, (max-width: 1555px) 100vw, 1555px" /></li>



<li>Create a database master key in the master database <img loading="lazy" decoding="async" width="1112" height="219" class="wp-image-20595" style="width: 1200px" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_2-1.jpg" alt="" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_2-1.jpg 1112w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_2-1-300x59.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_2-1-1024x202.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_2-1-768x151.jpg 768w" sizes="auto, (max-width: 1112px) 100vw, 1112px" /></li>



<li>Enable Availability group feature <img loading="lazy" decoding="async" width="673" height="152" class="wp-image-20596" style="width: 700px" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_3-1.jpg" alt="" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_3-1.jpg 673w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_3-1-300x68.jpg 300w" sizes="auto, (max-width: 673px) 100vw, 673px" /></li>



<li>Enable trace flags to optimize performance (not required):<ul><li><a href="https://support.microsoft.com/en-us/topic/kb3009974-fix-slow-synchronization-when-disks-have-different-sector-sizes-for-primary-and-secondary-replica-log-files-in-sql-server-ag-and-logshipping-environments-ed181bf3-ce80-b6d0-f268-34135711043c">-T1800</a>: This trace flag optimizes performance when the log files for the primary and secondary replicas in an availability group are hosted on disks with different sector sizes, such as 512 bytes and 4K otherwise it&#8217;s not required</li></ul> 
<ul class="wp-block-list">
<li>-T9567: This trace flag enables compression of the data stream for availability groups during automatic seeding. The compression increases the load on the processor but can significantly reduce transfer time during seeding.</li>
</ul>
</li>
</ul>



<p>Don’t forget to restart the SQL engine if you have just enable Always On Availability Groups feature for your SQL service.</p>



<p>Let’s start to replicate the AdventureWorks database from our on-premise instance to an Azure SQL Managed Instance. The source database must be in Full recovery model and have at least one full backup.<br>Go to the source database, right click on it and select “Azure SQL Managed Instance link” and “Replicate database”:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="669" height="468" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_10-1.jpg" alt="" class="wp-image-20601" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_10-1.jpg 669w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_10-1-300x210.jpg 300w" sizes="auto, (max-width: 669px) 100vw, 669px" /></figure>



<p>An introduction slide explains us the goal, some scenarios and the requirements of this new feature:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="823" height="749" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_11.jpg" alt="" class="wp-image-20553" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_11.jpg 823w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_11-300x273.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_11-768x699.jpg 768w" sizes="auto, (max-width: 823px) 100vw, 823px" /></figure>



<p>Requirements are now checked and they are all met here, for the server but also for Availability Group where the database master key is available:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="737" height="683" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_12.jpg" alt="" class="wp-image-20554" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_12.jpg 737w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_12-300x278.jpg 300w" sizes="auto, (max-width: 737px) 100vw, 737px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="761" height="784" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_12_2.jpg" alt="" class="wp-image-20602" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_12_2.jpg 761w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_12_2-291x300.jpg 291w" sizes="auto, (max-width: 761px) 100vw, 761px" /></figure>



<p>We can now select one or more databases to replicate to the Azure SQL Managed instance via the link feature. We select here the AdventureWorks2019 database which meets the requirements, it’s means Full recovery model and a Full backup executed:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="765" height="785" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_13-1.jpg" alt="" class="wp-image-20604" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_13-1.jpg 765w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_13-1-292x300.jpg 292w" sizes="auto, (max-width: 765px) 100vw, 765px" /></figure>



<p>It’s time to sign in to our Azure Subscription:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="761" height="782" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_14-1.jpg" alt="" class="wp-image-20605" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_14-1.jpg 761w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_14-1-292x300.jpg 292w" sizes="auto, (max-width: 761px) 100vw, 761px" /></figure>



<p>Once done we need to select the Managed Instance information which will be our target: </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="762" height="783" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_15-1.jpg" alt="" class="wp-image-20607" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_15-1.jpg 762w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_15-1-292x300.jpg 292w" sizes="auto, (max-width: 762px) 100vw, 762px" /></figure>



<p>On the following screen we have lots of information:</p>



<ul class="wp-block-list">
<li>The name and the port of the endpoint which will be created for the database mirroring</li>



<li>the certificate which will be created with an expiry date</li>



<li>the names for the Availability group and for the Distributed Availability group</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="762" height="782" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_16.jpg" alt="" class="wp-image-20609" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_16.jpg 762w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_16-292x300.jpg 292w" sizes="auto, (max-width: 762px) 100vw, 762px" /></figure>



<p>A last check to the choices made and click Finish to start the process:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="763" height="783" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_17.jpg" alt="" class="wp-image-20611" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_17.jpg 763w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_17-292x300.jpg 292w" sizes="auto, (max-width: 763px) 100vw, 763px" /></figure>



<p>All steps succeeded:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="761" height="778" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_18.jpg" alt="" class="wp-image-20612" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_18.jpg 761w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_18-293x300.jpg 293w" sizes="auto, (max-width: 761px) 100vw, 761px" /></figure>



<p>I have now my AdventureWorks database replicated on my Managed instance and ready for read-only workload.<br>If I check my Distributed Availability group dashboard, I can see that my first replica is my Availability group and my second is the Managed Instance. My AdventureWorks database is synchronized and the last hardened time occurs during my first synchronization.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="440" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_19-1024x440.jpg" alt="" class="wp-image-20614" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_19-1024x440.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_19-300x129.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_19-768x330.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_19.jpg 1042w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>If I update the column JobTitle on my Employee table on my Source instance, the update will be replicated asynchronously to my secondary database.<br>My replica database before the update:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="911" height="661" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_20.jpg" alt="" class="wp-image-20617" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_20.jpg 911w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_20-300x218.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_20-768x557.jpg 768w" sizes="auto, (max-width: 911px) 100vw, 911px" /></figure>



<p>I run my script on my primary database:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="954" height="704" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_21.jpg" alt="" class="wp-image-20618" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_21.jpg 954w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_21-300x221.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_21-768x567.jpg 768w" sizes="auto, (max-width: 954px) 100vw, 954px" /></figure>



<p>After some seconds my secondary is again synchronized:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="829" height="679" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_22.jpg" alt="" class="wp-image-20619" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_22.jpg 829w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_22-300x246.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_22-768x629.jpg 768w" sizes="auto, (max-width: 829px) 100vw, 829px" /></figure>



<p>And if I look on my Distributed Availability group, I can see the details with my last hardened time:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="777" height="425" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_23.jpg" alt="" class="wp-image-20621" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_23.jpg 777w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_23-300x164.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_23-768x420.jpg 768w" sizes="auto, (max-width: 777px) 100vw, 777px" /></figure>



<p>We cannot see that the database on the Managed Instance is not a &#8220;Standard&#8221; one, there is no information written after the database name like Synchronized as it is the case with a secondary database in an Availability group. Nevertheless if we try to run an update statement again this database we receive an error message:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="893" height="257" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_24.jpg" alt="" class="wp-image-20623" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_24.jpg 893w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_24-300x86.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/MIL_24-768x221.jpg 768w" sizes="auto, (max-width: 893px) 100vw, 893px" /></figure>



<p>This new Azure Managed Instance link feature is really interesting to off-load analytics and reporting to Azure but can also be used in a migration scenario with near to zero downtime.<br>It&#8217;s also a good way to create a first hybrid scenario before moving to the Cloud.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/azure-sql-managed-instance-link-feature-overview/">Azure SQL Managed Instance link feature overview</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/azure-sql-managed-instance-link-feature-overview/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>dbachecks: how to add your own checks</title>
		<link>https://www.dbi-services.com/blog/dbachecks-how-to-add-your-own-checks/</link>
					<comments>https://www.dbi-services.com/blog/dbachecks-how-to-add-your-own-checks/#respond</comments>
		
		<dc:creator><![CDATA[Stéphane Savorgnano]]></dc:creator>
		<pubDate>Thu, 17 Nov 2022 12:49:41 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[dbachecks]]></category>
		<category><![CDATA[dbatools]]></category>
		<category><![CDATA[own checks]]></category>
		<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=20482</guid>

					<description><![CDATA[<p>As a DBA I used more and more the dbachecks at customer place to validate environments. It became part of our DBA toolkit at the same place than its famous brother the dbatools.My colleague Steven Naudet did a blog-post some weeks ago about it and today I will show you how is it possible to [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/dbachecks-how-to-add-your-own-checks/">dbachecks: how to add your own checks</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>As a DBA I used more and more the <a href="https://dbachecks.readthedocs.io/">dbachecks</a> at customer place to validate environments. It became part of our DBA toolkit at the same place than its famous brother the <a href="https://dbatools.io/">dbatools</a>.<br>My colleague Steven Naudet did a <a href="https://www.dbi-services.com/blog/validate-your-sql-server-infrastructure-with-dbachecks/">blog-post</a> some weeks ago about it and today I will show you how is it possible to add your own checks if you cannot find the one you need in the quite long list available for the moment and which is continuously growing.</p>



<p>Before to create your own checks you can read this <a href="https://github.com/dataplat/dbachecks/wiki">wiki</a> which is dedicated to dbachecks development and available on the dbachecks main page. It will give you some advice and standard development guidelines.</p>



<p>To create my new checks I will first create a new file which will contain those checks, lets name it SelfChecks.Tests.ps1.<br>I would like to add two checks:</p>



<ul class="wp-block-list">
<li>The first one will check the health of my Availability Groups which have to be synchronized and healthy.<br>I will tag this check DBinAG.</li>
</ul>



<ul class="wp-block-list">
<li>The second one will check that all the user databases of my instance are part of an Availability Group with exception of some specifics ones like our dbi_tools database for example.<br>I will tag this one DBHealthyInAG.</li>
</ul>



<p>The PowerShell script will be the following:</p>



<pre class="wp-block-code"><code>$filename = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "")

$DBinAG = "IF (SERVERPROPERTY ('IsHadrEnabled') = 1)
BEGIN
	SELECT  name, state_desc
	FROM    sys.databases
	WHERE    name    NOT IN
		(   SELECT  DISTINCT
					drcs.database_name              AS name
			FROM                master.sys.availability_groups                      AS ag
				LEFT OUTER JOIN master.sys.dm_hadr_availability_group_states        AS ags
							ON    ag.group_id       = ags.group_id
				INNER JOIN      master.sys.availability_replicas                    AS ar
							ON   ag.group_id        = ar.group_id
				INNER JOIN      master.sys.dm_hadr_availability_replica_states      AS ars
							ON     ar.replica_id    = ars.replica_id
							AND    ars.is_local     = 1
				INNER JOIN      master.sys.dm_hadr_database_replica_cluster_states  AS drcs
							ON     ars.replica_id   = drcs.replica_id
				LEFT OUTER JOIN master.sys.dm_hadr_database_replica_states          AS dbrs
							ON      drcs.replica_id         = dbrs.replica_id
							AND     drcs.group_database_id  = dbrs.group_database_id
			WHERE   ISNULL(ars.role, 3)                 = 1
			OR      ISNULL(drcs.is_database_joined, 0)  = 1
		)
	--AND     dbid  
	AND     name    NOT IN  ('master'
							,'tempdb'
							,'model'
							,'msdb'
							-- add below your exceptions
							,'dbi_tools'
							)
	AND     state_desc  != 'OFFLINE'
	ORDER BY
			name    ASC
END";

(Get-Instance).ForEach{

    Describe –Name "All databases should be in an AG" -Tags DBinAG, $filename {
        Context –Name "All databases should be in an AG on $psitem" {
            It "should have 0 database outide of an AG" {
                Invoke-DbaQuery -SqlInstance $psitem -Query $DBinAG |  
                Measure-Object |
                    Select-Object –ExpandProperty Count |
                    Should -Be 0 –Because "All databases should be in an AG"
            }
        }
    }
}

$DBHealthyInAG = "IF (SERVERPROPERTY ('IsHadrEnabled') = 1)
BEGIN
SELECT  ag.name                             AS &#091;AGName]
        ,adc.&#091;database_name]                AS &#091;DBName]
        ,ar.replica_server_name             AS &#091;ReplicaServerName]
        ,drs.synchronization_state_desc     AS &#091;SynchronizationState]
        ,drs.synchronization_health_desc    AS &#091;SynchronizationHealth]
        ,drs.is_local                       AS &#091;IsLocal]
        ,ar.availability_mode_desc          AS &#091;AvailabilityMode]
FROM    sys.dm_hadr_database_replica_states                 AS drs WITH (NOLOCK)
        INNER JOIN sys.availability_databases_cluster       AS adc WITH (NOLOCK)
            ON  drs.group_id            = adc.group_id
            AND drs.group_database_id   = adc.group_database_id
        INNER JOIN sys.availability_groups                  AS ag WITH (NOLOCK)
            ON  ag.group_id             = drs.group_id
        INNER JOIN sys.availability_replicas                AS ar WITH (NOLOCK)
            ON  drs.group_id            = ar.group_id
            AND drs.replica_id          = ar.replica_id
WHERE   1=1
AND NOT (   drs.synchronization_state_desc  = 'SYNCHRONIZED'
        AND drs.synchronization_health_desc = 'HEALTHY'
        )
ORDER BY
        ag.name
        ,ar.replica_server_name
        ,adc.&#091;database_name]        OPTION (RECOMPILE)
END";

(Get-Instance).ForEach{

    Describe –Name "All databases should be synchronized and healthy in an AG" –Tags DBHealthyInAG, $filename {
        Context –Name "All databases should be synchronized and healthy in an AG on $psitem" {
            It "should have 0 unsynchronized and unhealthy databases" {
                Invoke-DbaQuery -SqlInstance $psitem -Query $DBHealthyInAG |  
                Measure-Object |
                    Select-Object –ExpandProperty Count |
                    Should -Be 0 –Because "All databases need to be synchronized and healthy"
            }
        }
    }
}</code></pre>



<p>When we install the dbachecks, Pester tests &amp; checks are stored in a particular folder and to find where we can run the cmdlet:</p>



<pre class="wp-block-code"><code>Get-DbcConfig -Name app.checkrepos</code></pre>



<p>Result is:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="751" height="85" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/Picture1.png" alt="" class="wp-image-20483" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/Picture1.png 751w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/Picture1-300x34.png 300w" sizes="auto, (max-width: 751px) 100vw, 751px" /></figure>



<p>To add my checks to the list of the available checks I need to add the location of my file to this configuration value.<br>I can do it with the cmdlet:</p>



<pre class="wp-block-code"><code>Set-DbcConfig -Name app.checkrepos -Value 'C:\dbachecks\OwnChecks\' -Append</code></pre>



<p>Now, my folder has been added to the check repo:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="752" height="90" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/Picture2.png" alt="" class="wp-image-20484" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/Picture2.png 752w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/Picture2-300x36.png 300w" sizes="auto, (max-width: 752px) 100vw, 752px" /></figure>



<p>I&#8217;m able to call my two new tags as I would do for &#8220;existing&#8221; ones and the result is:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="752" height="257" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/Picture3.png" alt="" class="wp-image-20487" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/Picture3.png 752w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/11/Picture3-300x103.png 300w" sizes="auto, (max-width: 752px) 100vw, 752px" /></figure>



<p>All my databases are part of an Always On Availability Group and my AG are healthy &amp; synchronized <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>I was able to add my two new checks and if I need another ones, I can add them to my file.</p>



<p>Quite easy no?</p>
<p>L’article <a href="https://www.dbi-services.com/blog/dbachecks-how-to-add-your-own-checks/">dbachecks: how to add your own checks</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/dbachecks-how-to-add-your-own-checks/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Patch a SQL Server instance automatically with PowerShell</title>
		<link>https://www.dbi-services.com/blog/patch-a-sql-server-instance-automatically-with-powershell/</link>
					<comments>https://www.dbi-services.com/blog/patch-a-sql-server-instance-automatically-with-powershell/#comments</comments>
		
		<dc:creator><![CDATA[Stéphane Savorgnano]]></dc:creator>
		<pubDate>Fri, 11 Nov 2022 14:00:26 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Apply CU]]></category>
		<category><![CDATA[apply SP]]></category>
		<category><![CDATA[automatic]]></category>
		<category><![CDATA[dbatools]]></category>
		<category><![CDATA[Patching]]></category>
		<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=20420</guid>

					<description><![CDATA[<p>In my last blog post I explained how to automatically download the last Service Pack and Cumulative Update for all versions of SQL Server.Here I will show you how to patch your SQL Server instances automatically with some cmdlets from the dbatools. First we need a SQL Credential to be able to remotely connect to [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/patch-a-sql-server-instance-automatically-with-powershell/">Patch a SQL Server instance automatically with PowerShell</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In my <a href="https://www.dbi-services.com/blog/how-to-automatically-download-last-sql-server-sp-and-cu-with-powershell/">last blog post</a> I explained how to automatically download the last Service Pack and Cumulative Update for all versions of SQL Server.<br>Here I will show you how to patch your SQL Server instances automatically with some cmdlets from the <a href="https://dbatools.io/commands/">dbatools</a>.</p>



<p>First we need a SQL Credential to be able to remotely connect to the server where we will patch our instances:</p>



<pre class="wp-block-code"><code>$OScred = Get-Credential;</code></pre>



<p>Once done we will have to find the instances located on the server. Here multiple possibilities: </p>



<ul class="wp-block-list">
<li>you can create just a list with your instances $InstanceList = @(&#8216;Thor90\SQL19_1&#8242;,&#8217;Thor91\SQL19_1&#8217;);</li>



<li>you can search the instance on your server with $InstanceList = Get-DbaService | where-object { $_.ServiceType -eq &#8216;Engine&#8217; -and $_.State -eq &#8216;Running&#8217; };</li>



<li>you can also select your instance in your CMS with $InstanceList = Get-DbaRegServer -SqlInstance &#8216;Thor90\CMS&#8217; -Group &#8220;Prod\SQL2019&#8221;; (my case)</li>
</ul>



<p>Now that we have our list of instances, we will loop on those instances, run a quick connection test and if it succeeds patch our instance.<br>To patch the instance, I will use 2 dbatools cmdlets which are:</p>



<ul class="wp-block-list">
<li><a href="https://docs.dbatools.io/Get-DbaBuildReference#Get-DbaBuild">Get-DbaBuildReference</a>: will return information about the instance like the SP, CU and used with the -Update switch will update the local reference with the most up to date one </li>



<li><a href="https://docs.dbatools.io/Update-DbaInstance">Update_DbaInstance</a>: will start a process which will update a SQL Server instance to a specified version<br>This cmdlet is really powerful with lots of parameter, possible options and also risks, I let you go through its definition.<br>I will use it  in my example with the -version parameter which is the target version I want to reach, with the switch -Restart to automatically restart my server after the installation, the parameter -Path to specif the location where my patches have been downloaded, the -Credential to have permission to log remotely to my server and -Confirm to false to run without confirmation.</li>
</ul>



<p>I&#8217;ll use also some logging cmdlets wrote by myself which are Out-Log, Add-Warning and Add-Error.</p>



<p>The loop on all my instance list will look like:</p>



<pre class="wp-block-code"><code>ForEach ($Instance in $InstanceList){
     Out-Log -Message "Patching instance $($Instance.InstanceName)" -LogFile $LogFile
        
    $connection = Test-DBAConnection -SqlInstance $instance.InstanceName -WarningVariable warningvar;
    Add-Warning -Warning $Warningvar;
    If ($connection -and $connection.ConnectSuccess) {
        Out-Log -Message "Connection to instance $($Instance.InstanceName) succeeded" -LogFile $LogFile

        #Update the instance build which is used by the cmdlet Update-DbaInstance
        Out-Log -Message "Update the build of the instance $($Instance.InstanceName)" -LogFile $LogFile
        Get-DbaBuildReference -SqlInstance $instance.InstanceName -Update -WarningVariable warningvar;
        Add-Warning -Warning $Warningvar;

        Out-Log -Message "Start patching of the instance $Instance.InstanceName" -LogFile $LogFile
        try {
            $res = Update-DbaInstance -ComputerName $Instance.ComputerName -InstanceName $Instance.InstanceName -credential $OScred -Version CU16 -Path \\Thor90\sources\SQL2019 -Restart -Confirm:$false -WarningVariable warningvar;
            Add-Warning -Warning $Warningvar;
        }
        catch {
            Add-Error -Message "Patching of the instance $instance$Instance.InstanceName not possible, Exception: $($error&#091;0].Exception.Message)";
        }

    }
    Else {
        Add-Error -Message "Impossible to connect to the instance $instance$Instance.InstanceName.";
    }
}</code></pre>



<p>This script will patch my production SQL Server 2019 instances to CU16 with the KB located on my share drive \\Thor90\sources\SQL2019 and restart the server once succeeded.<br>Lots of scenario can be covered by using this cmdlet, I let you playing with it, but on your test environment first <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>L’article <a href="https://www.dbi-services.com/blog/patch-a-sql-server-instance-automatically-with-powershell/">Patch a SQL Server instance automatically with PowerShell</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/patch-a-sql-server-instance-automatically-with-powershell/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>How to automatically download last SQL Server SP and CU with PowerShell</title>
		<link>https://www.dbi-services.com/blog/how-to-automatically-download-last-sql-server-sp-and-cu-with-powershell/</link>
					<comments>https://www.dbi-services.com/blog/how-to-automatically-download-last-sql-server-sp-and-cu-with-powershell/#comments</comments>
		
		<dc:creator><![CDATA[Stéphane Savorgnano]]></dc:creator>
		<pubDate>Tue, 06 Sep 2022 12:51:10 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[CU]]></category>
		<category><![CDATA[Cumulative Update]]></category>
		<category><![CDATA[dbatools]]></category>
		<category><![CDATA[download]]></category>
		<category><![CDATA[patching problem]]></category>
		<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Service Pack]]></category>
		<category><![CDATA[SP]]></category>
		<category><![CDATA[SQL Server]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=18845</guid>

					<description><![CDATA[<p>One of my customer asked me some days ago to create a PowerShell script to patch its SQL Server instances. After a reflection time, the first step will be to download the last Service Pack and the last Cumulative Update depending of the SQL Server version and after to apply those patches to the SQL [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/how-to-automatically-download-last-sql-server-sp-and-cu-with-powershell/">How to automatically download last SQL Server SP and CU with PowerShell</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>One of my customer asked me some days ago to create a PowerShell script to patch its SQL Server instances. After a reflection time, the first step will be to download the last Service Pack and the last Cumulative Update depending of the SQL Server version and after to apply those patches to the SQL Server instances. In this blog post I will explain how I managed to run those downloads.</p>



<p>As usual, each time I have to deal with SQL Server and PowerShell I have a look to the famous <a href="https://dbatools.io/">dbatools</a> pages to check if I can find useful cmdlets. Jackpot, somes attired my attention, I will present you those later on.</p>



<p>I want to download the last Cumulative Update and Service pack for the SQL Server versions 2014 to 2019. I create a list with the RTM build of each version:</p>



<pre class="wp-block-code"><code>#List of build for the RTM version from 2014 to 2019
$RTMBuilds = @('12.0.2000','13.0.1601','14.0.1000','15.0.2000');</code></pre>



<p> I need also a share path to save my SP and CU:</p>



<pre class="wp-block-code"><code>#Path where files will be downloaded
$Path = "\\THOR90\Sources";</code></pre>



<p>I will loop on each build and use the dbatool cmdlet <a href="https://docs.dbatools.io/Test-DbaBuild">Test-DbaBuild</a> to find the last Cumulative Update for the RTM version in the loop. This cmdlet used with the parameters -Build with my RTM build number and -MaxBehind &#8220;0CU&#8221; provides me the target build number of the last Cumulative Update for my SQL Server version. There is just a small exception for the moment with SQL Server 2016 where the last patch is a Service Pack and there is no CU for the moment for this Service Pack. In this case, I need to check for the version -1 of the last SP and find the last CU for this SP, it means change the parameter  -MaxBehind to &#8220;1SP 0CU&#8221;. Here is the code:</p>



<pre class="wp-block-code"><code>#Download last CU
$res = Test-DbaBuild -Build $RTMBuild -MaxBehind "0CU";
#$test special case where the last build is a SP, to download the last CU before this SP
if ($res.CUTarget -eq $null) {
    $res = Test-DbaBuild -Build $RTMBuild -MaxBehind "1SP 0CU"; 
}</code></pre>



<p>This code applied to the RTM build of SQL Server 2016 returns:</p>



<p>Build : 13.0.1601<br>BuildLevel : 13.0.1601<br>BuildTarget : 13.0.5888<br>Compliant : False<br>CULevel :<br>CUTarget : CU17<br>KBLevel :<br>MatchType : Exact<br>MaxBehind : 1SP 0CU<br>NameLevel : 2016<br>SPLevel : RTM<br>SPTarget : SP2<br>SupportedUntil : 1/9/2018 12:00:00 AM</p>



<p>It means that the last CU available for SQL Server 2016 is the CU17.<br>Now, I need to find the KB number corresponding to the build number of my CU17. I will use the dbatools cmdlet <a href="https://docs.dbatools.io/Get-DbaBuildReference#Get-DbaBuild">Get-DbaBuildReference</a> to retrieve it.<br>Once I have my KB, I will use another dbatools cmdlet named <a href="https://docs.dbatools.io/Get-DbaKbUpdate">Get-DbaKbUpdate</a> which will parse the catalog.update.microsoft.com and grab details for KB files.<br>I can have multiple links to download my KB such as a link for the 32 bits version and another one for the 64 bits. I will use the 64 bits and select just the file name to download it with the last dbatools cmdlet used in this blog.</p>



<pre class="wp-block-code"><code>$BuildTarget = $res.BuildTarget;
$kb = (Get-DbaBuildReference $BuildTarget).KBLevel;
$resKB = Get-DbaKbUpdate $kb;
    
#Find the file name and check that it is the x64
$file = ($resKB.Link | Select-Object -Last 1) -split ('/') | Select-Object -last 1;
if ($file.IndexOf("x64") -eq -1)
    { $file = ($resKB.Link | Select-Object -First 1) -split ('/') | Select-Object -last 1; }</code></pre>



<p>As I have now my file name, I will download this file from Microsoft and save it on my share with the dbatools cmdlet <a href="https://docs.dbatools.io/Save-DbaKbUpdate">Save-DbaKbUpdate</a>. If the file already exists on my share I will not download it.</p>



<pre class="wp-block-code"><code>#Download the file if it is not already done
if (!(Test-Path "$Path\$file" -PathType Leaf)) {
    Write-Output "downloading $file"
    Save-DbaKbUpdate $kb -Path $Path
    } 
    else {write-output "File $file already exists" }</code></pre>



<p>This first part of the script download the last CUs, to download the last SPs I will use exactly the same script when the major build version is lower or equal to 13 (SQL Server 2016) and save them also to my share:</p>



<pre class="wp-block-code"><code>################################### SP
#Download the last SP if version &lt;=2016
if ($res.BuildTarget.Major -le 13) {
    $res = Test-DbaBuild -Build $RTMBuild -MaxBehind "0SP";
    $BuildTarget = $res.BuildTarget;
    $kb = (Get-DbaBuildReference $BuildTarget).KBLevel;
    $resKB = Get-DbaKbUpdate $kb;
        
    #Find the file name and check that it is the x64
    $file = ($resKB.Link | Select-Object -Last 1) -split ('/') | Select-Object -last 1;
    if ($file.IndexOf("x64") -eq -1)
        { $file = ($resKB.Link | Select-Object -First 1) -split ('/') | Select-Object -last 1; }
        
    #Download the file if it is not already done
    if (!(Test-Path "$Path\$file" -PathType Leaf)) {
        Write-Output "downloading $file"
        Save-DbaKbUpdate $kb -Path $Path
        } 
        else {write-output "File $file already exists" }
}</code></pre>



<p>Finally, I have all my Cumulative Updates and Service Packs available on my share. I can schedule the complete script every month to have always on my share the last available CU and SP for the SQL Server versions I have on my environment.<br>The next step will be to apply those patches to my SQL Server instances.<br>It will be the subject of my next blog post.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/how-to-automatically-download-last-sql-server-sp-and-cu-with-powershell/">How to automatically download last SQL Server SP and CU with PowerShell</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/how-to-automatically-download-last-sql-server-sp-and-cu-with-powershell/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Contained Availability Groups error</title>
		<link>https://www.dbi-services.com/blog/contained-availability-groups-error/</link>
					<comments>https://www.dbi-services.com/blog/contained-availability-groups-error/#respond</comments>
		
		<dc:creator><![CDATA[Stéphane Savorgnano]]></dc:creator>
		<pubDate>Fri, 01 Jul 2022 08:10:46 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Contained Availability Groups]]></category>
		<category><![CDATA[Contained availability groups issue]]></category>
		<category><![CDATA[SQL Server 2022]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=17708</guid>

					<description><![CDATA[<p>During my testing about the new SQL Server 2022 feature Contained Availability group I faced an issue during the creation of the system databases on the secondary instance. I used the wizard to create a new Contained Availability Group and chose the Full database and lo backup synchronization method to restore database on my secondary [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/contained-availability-groups-error/">Contained Availability Groups error</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>During my testing about the new SQL Server 2022 feature Contained Availability group I faced an issue during the creation of the system databases on the secondary instance.</p>



<p>I used the wizard to create a new Contained Availability Group and chose the Full database and lo backup synchronization method to restore database on my secondary instance.<br>I generated the script and ran it.</p>



<p>I was a bit confused to receive an error message.<br>It looks like that the master database has to be joined and recovered before the user databases in a Contained Availability group: </p>



<p class="has-vivid-red-color has-text-color">Msg 47148, Level 16, State 13, Line 180<br>Cannot join database &#8216;AdventureWorks&#8217; to contained availability group &#8216;AAG_Thor9091_C01&#8217;. Before joining other databases to contained availability group, contained availability group master database has to be joined and recovered. Make sure contained availability group master database has been joined and recovered, then retry the operation.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="333" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_3-1024x333.jpg" alt="" class="wp-image-17709" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_3-1024x333.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_3-300x97.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_3-768x250.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_3-1536x499.jpg 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_3.jpg 1868w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>If I check on my primary, my Contained Availability Group looks good with my master &amp; msdb system databases and my AdventureWorks database are marked as Synchronized:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="394" height="454" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_1.jpg" alt="" class="wp-image-17710" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_1.jpg 394w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_1-260x300.jpg 260w" sizes="auto, (max-width: 394px) 100vw, 394px" /></figure>



<p>But if I check the dashboard of my Availability Group or even my secondary instance I can see that my AdventureWorks database is on Restoring mode, as pointed out by the previous error, and I have no master nor msdb databases:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="394" height="455" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_2.jpg" alt="" class="wp-image-17711" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_2.jpg 394w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_2-260x300.jpg 260w" sizes="auto, (max-width: 394px) 100vw, 394px" /></figure>



<p>If I check the SQL logs on the primary instance I can see that my master and msdb database have been backed up but I have no trace of the restore on my secondary instance compare to the AdventureWorks database for which I can find a restore operation.</p>



<p>I dropped my Contained Availability Group and recreate the exactly same one but with the seeding synchronization method, I generate the scripts and run it:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="455" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_4-1024x455.jpg" alt="" class="wp-image-17712" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_4-1024x455.jpg 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_4-300x133.jpg 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_4-768x341.jpg 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_4-1536x682.jpg 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2022/06/CAG_4.jpg 1889w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>No all is working perfectly, my master and msdb databases have been restored after some seconds to my secondary, the DMV dm_hadr_physical_seeding_stats shows that the success of the seeding method and my Contained Availability Group is synchronized on my 2 nodes.</p>



<p>I did those test with <a href="https://info.microsoft.com/ww-landing-sql-server-2022.html">SQL Server 2022 CTP2.0</a> so no doubt that this issue will be fixed for the RTM version <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br>You can also read the blog of my colleague Steven on <a href="https://www.dbi-services.com/blog/first-look-at-sql-server-2022-contained-availability-groups">Contained Availability Group</a> to have an overview of this feature.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/contained-availability-groups-error/">Contained Availability Groups error</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/contained-availability-groups-error/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 
Lazy Loading (feed)

Served from: www.dbi-services.com @ 2026-04-27 00:56:22 by W3 Total Cache
-->