From b2857c241dd6bf615bc079eaff83f5d4d3518558 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:15:15 +0000 Subject: [PATCH 1/2] Add Get-DbaAgRingBuffer command for HADR ring buffer diagnostics Implements Get-DbaAgRingBuffer to query sys.dm_os_ring_buffers for the four HADR-specific ring buffer types used in Always On AG diagnostics: RING_BUFFER_HADRDBMGR_API, RING_BUFFER_HADRDBMGR_STATE, RING_BUFFER_HADRDBMGR_COMMIT, and RING_BUFFER_HADR_TRANSPORT_STATE. Returns raw XML records alongside metadata (EventTime, RingBufferType, RecordId) so callers have full access to the unsupported-but-useful diagnostic data without brittle type-specific XML parsing. Requires SQL Server 2012+ (MinimumVersion 11) as Always On was introduced in that release. Closes #9823 (do Get-DbaAgRingBuffer) Co-authored-by: Andreas Jordan --- dbatools.psd1 | 1 + dbatools.psm1 | 1 + public/Get-DbaAgRingBuffer.ps1 | 148 ++++++++++++++++++++++++++++ tests/Get-DbaAgRingBuffer.Tests.ps1 | 46 +++++++++ 4 files changed, 196 insertions(+) create mode 100644 public/Get-DbaAgRingBuffer.ps1 create mode 100644 tests/Get-DbaAgRingBuffer.Tests.ps1 diff --git a/dbatools.psd1 b/dbatools.psd1 index 98efc7c5b8f8..570801ce630f 100644 --- a/dbatools.psd1 +++ b/dbatools.psd1 @@ -196,6 +196,7 @@ 'Get-DbaAgHadr', 'Get-DbaAgListener', 'Get-DbaAgReplica', + 'Get-DbaAgRingBuffer', 'Get-DbaAvailabilityGroup', 'Get-DbaAvailableCollation', 'Get-DbaBackupDevice', diff --git a/dbatools.psm1 b/dbatools.psm1 index 1afe4f2ad1ab..011bc963c1bf 100644 --- a/dbatools.psm1 +++ b/dbatools.psm1 @@ -607,6 +607,7 @@ if ($PSVersionTable.PSVersion.Major -lt 5) { 'Mount-DbaDatabase', 'Dismount-DbaDatabase', 'Get-DbaAgReplica', + 'Get-DbaAgRingBuffer', 'Get-DbaAgDatabase', 'Get-DbaAgDatabaseReplicaState', 'Get-DbaModule', diff --git a/public/Get-DbaAgRingBuffer.ps1 b/public/Get-DbaAgRingBuffer.ps1 new file mode 100644 index 000000000000..551a9b5e03f0 --- /dev/null +++ b/public/Get-DbaAgRingBuffer.ps1 @@ -0,0 +1,148 @@ +function Get-DbaAgRingBuffer { + <# + .SYNOPSIS + Retrieves Always On availability group diagnostic data from SQL Server's internal HADR ring buffers. + + .DESCRIPTION + This command queries sys.dm_os_ring_buffers for HADR-specific ring buffer types to provide diagnostic + information about Always On availability groups. These ring buffers record state transitions, role changes, + commit activity, and transport state events useful for troubleshooting AG health and failover issues. + + As noted in Microsoft's documentation, the ring buffers are not officially supported, but they provide + valuable post-mortem diagnostic data, especially when SQL Server stops responding or has crashed. + + Reference: https://learn.microsoft.com/en-us/sql/database-engine/availability-groups/windows/always-on-ring-buffers + + .PARAMETER SqlInstance + The target SQL Server instance or instances. + + .PARAMETER SqlCredential + Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential). + + Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported. + + For MFA support, please use Connect-DbaInstance. To use: + $cred = Get-Credential, this pass this $cred to the param. + + Windows Authentication will be used if SqlCredential is not specified. To connect as a different Windows user, run PowerShell as that user. + + .PARAMETER RingBufferType + Specifies which HADR ring buffer types to query. Defaults to all four HADR ring buffer types. + + Valid values: + - RING_BUFFER_HADRDBMGR_API : State transitions at the API level + - RING_BUFFER_HADRDBMGR_STATE : Database manager state change records + - RING_BUFFER_HADRDBMGR_COMMIT : Commit-level activity records + - RING_BUFFER_HADR_TRANSPORT_STATE: Connection and transport state transitions + + .PARAMETER EnableException + By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. + This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. + Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. + + .OUTPUTS + PSCustomObject + + Returns one object per ring buffer record retrieved from the SQL Server instance. + + Properties: + - ComputerName : The computer name of the SQL Server instance + - InstanceName : The SQL Server instance name + - SqlInstance : The full SQL Server instance name (computer\instance) + - RingBufferType : The type of ring buffer (e.g. RING_BUFFER_HADRDBMGR_API) + - RecordId : The unique record identifier from the ring buffer entry + - EventTime : Approximate DateTime of the event (in local server time) + - Record : The raw XML record containing event-specific diagnostic fields + + .NOTES + Tags: Diagnostic, Buffer, HADR, AvailabilityGroup, AG, AlwaysOn + Author: the dbatools team + Claude + + Website: https://dbatools.io + Copyright: (c) 2018 by dbatools, licensed under MIT + License: MIT https://opensource.org/licenses/MIT + + .LINK + https://dbatools.io/Get-DbaAgRingBuffer + + .EXAMPLE + PS C:\> Get-DbaAgRingBuffer -SqlInstance sql2019 + + Returns all HADR ring buffer records from the sql2019 instance. + + .EXAMPLE + PS C:\> Get-DbaAgRingBuffer -SqlInstance sql2019 -RingBufferType RING_BUFFER_HADRDBMGR_API + + Returns only RING_BUFFER_HADRDBMGR_API records from the sql2019 instance. + + .EXAMPLE + PS C:\> Get-DbaAgRingBuffer -SqlInstance sql2019 -RingBufferType RING_BUFFER_HADRDBMGR_API, RING_BUFFER_HADR_TRANSPORT_STATE + + Returns API and transport state records from sql2019. + + .EXAMPLE + PS C:\> 'sql2019', 'sql2022' | Get-DbaAgRingBuffer + + Returns all HADR ring buffer records from sql2019 and sql2022. + #> + [CmdletBinding()] + Param ( + [parameter(Mandatory, ValueFromPipeline)] + [DbaInstance[]]$SqlInstance, + [PSCredential]$SqlCredential, + [ValidateSet("RING_BUFFER_HADRDBMGR_API", "RING_BUFFER_HADRDBMGR_STATE", "RING_BUFFER_HADRDBMGR_COMMIT", "RING_BUFFER_HADR_TRANSPORT_STATE")] + [string[]]$RingBufferType, + [switch]$EnableException + ) + + process { + if (Test-FunctionInterrupt) { return } + foreach ($instance in $SqlInstance) { + try { + $server = Connect-DbaInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 11 + } catch { + Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue + } + + $currentTimestamp = ($server.Query("SELECT cpu_ticks / CONVERT(FLOAT, (cpu_ticks / ms_ticks)) AS TimeStamp FROM sys.dm_os_sys_info"))[0] + Write-Message -Level Verbose -Message "Using current timestamp of $currentTimestamp" + + if ($RingBufferType) { + $typeList = ($RingBufferType | ForEach-Object { "N'$_'" }) -join ", " + } else { + $typeList = "N'RING_BUFFER_HADRDBMGR_API', N'RING_BUFFER_HADRDBMGR_STATE', N'RING_BUFFER_HADRDBMGR_COMMIT', N'RING_BUFFER_HADR_TRANSPORT_STATE'" + } + + $sql = "WITH HadrRingBuffer AS + ( + SELECT + ring_buffer_type, + timestamp, + CONVERT(XML, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type IN ($typeList) + ) + SELECT + SERVERPROPERTY('ServerName') AS ServerName, + ring_buffer_type, + record.value('(./Record/@id)[1]', 'int') AS record_id, + DATEADD(ms, -1 * ($currentTimestamp - [timestamp]), GETDATE()) AS EventTime, + record + FROM HadrRingBuffer + ORDER BY EventTime DESC;" + + Write-Message -Level Verbose -Message "Executing SQL Statement: $sql" + foreach ($row in $server.Query($sql)) { + [PSCustomObject]@{ + ComputerName = $server.ComputerName + InstanceName = $server.ServiceName + SqlInstance = $server.DomainInstanceName + RingBufferType = $row.ring_buffer_type + RecordId = $row.record_id + EventTime = $row.EventTime + Record = $row.record + } + } + } + } +} diff --git a/tests/Get-DbaAgRingBuffer.Tests.ps1 b/tests/Get-DbaAgRingBuffer.Tests.ps1 new file mode 100644 index 000000000000..658b5f3c1714 --- /dev/null +++ b/tests/Get-DbaAgRingBuffer.Tests.ps1 @@ -0,0 +1,46 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0" } +param( + $ModuleName = "dbatools", + $CommandName = "Get-DbaAgRingBuffer", + $PSDefaultParameterValues = $TestConfig.Defaults +) + +Describe $CommandName -Tag UnitTests { + Context "Parameter validation" { + It "Should have the expected parameters" { + $hasParameters = (Get-Command $CommandName).Parameters.Values.Name | Where-Object { $PSItem -notin ("WhatIf", "Confirm") } + $expectedParameters = $TestConfig.CommonParameters + $expectedParameters += @( + "SqlInstance", + "SqlCredential", + "RingBufferType", + "EnableException" + ) + Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $hasParameters | Should -BeNullOrEmpty + } + } +} + +Describe $CommandName -Tag IntegrationTests { + Context "When retrieving HADR ring buffer data" { + It "Returns results with expected properties" { + $results = @(Get-DbaAgRingBuffer -SqlInstance $TestConfig.InstanceSingle) + if ($results.Count -gt 0) { + $results[0].PSObject.Properties.Name | Should -Contain "ComputerName" + $results[0].PSObject.Properties.Name | Should -Contain "InstanceName" + $results[0].PSObject.Properties.Name | Should -Contain "SqlInstance" + $results[0].PSObject.Properties.Name | Should -Contain "RingBufferType" + $results[0].PSObject.Properties.Name | Should -Contain "RecordId" + $results[0].PSObject.Properties.Name | Should -Contain "EventTime" + $results[0].PSObject.Properties.Name | Should -Contain "Record" + } + } + + It "Filters by RingBufferType when specified" { + $results = @(Get-DbaAgRingBuffer -SqlInstance $TestConfig.InstanceSingle -RingBufferType RING_BUFFER_HADRDBMGR_API) + foreach ($result in $results) { + $result.RingBufferType | Should -Be "RING_BUFFER_HADRDBMGR_API" + } + } + } +} From 50ad482f3cefc9b57fc92686f550aff4adad4e31 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:18:00 +0000 Subject: [PATCH 2/2] Get-DbaAgRingBuffer - Add CollectionMinutes parameter for consistency with Get-DbaCpuRingBuffer (do Get-DbaAgRingBuffer) Co-authored-by: Andreas Jordan --- public/Get-DbaAgRingBuffer.ps1 | 15 +++++++++++++-- tests/Get-DbaAgRingBuffer.Tests.ps1 | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/public/Get-DbaAgRingBuffer.ps1 b/public/Get-DbaAgRingBuffer.ps1 index 551a9b5e03f0..01d9bcdbe06b 100644 --- a/public/Get-DbaAgRingBuffer.ps1 +++ b/public/Get-DbaAgRingBuffer.ps1 @@ -35,6 +35,10 @@ function Get-DbaAgRingBuffer { - RING_BUFFER_HADRDBMGR_COMMIT : Commit-level activity records - RING_BUFFER_HADR_TRANSPORT_STATE: Connection and transport state transitions + .PARAMETER CollectionMinutes + Specifies how many minutes of historical data to retrieve from the ring buffer. Defaults to 60 minutes. + Use this to extend the analysis window when investigating longer-term AG issues or to focus on recent activity with shorter periods. + .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. @@ -68,12 +72,17 @@ function Get-DbaAgRingBuffer { .EXAMPLE PS C:\> Get-DbaAgRingBuffer -SqlInstance sql2019 - Returns all HADR ring buffer records from the sql2019 instance. + Returns HADR ring buffer records from the last 60 minutes from the sql2019 instance. + + .EXAMPLE + PS C:\> Get-DbaAgRingBuffer -SqlInstance sql2019 -CollectionMinutes 240 + + Returns HADR ring buffer records from the last 240 minutes from the sql2019 instance. .EXAMPLE PS C:\> Get-DbaAgRingBuffer -SqlInstance sql2019 -RingBufferType RING_BUFFER_HADRDBMGR_API - Returns only RING_BUFFER_HADRDBMGR_API records from the sql2019 instance. + Returns only RING_BUFFER_HADRDBMGR_API records from the last 60 minutes from the sql2019 instance. .EXAMPLE PS C:\> Get-DbaAgRingBuffer -SqlInstance sql2019 -RingBufferType RING_BUFFER_HADRDBMGR_API, RING_BUFFER_HADR_TRANSPORT_STATE @@ -92,6 +101,7 @@ function Get-DbaAgRingBuffer { [PSCredential]$SqlCredential, [ValidateSet("RING_BUFFER_HADRDBMGR_API", "RING_BUFFER_HADRDBMGR_STATE", "RING_BUFFER_HADRDBMGR_COMMIT", "RING_BUFFER_HADR_TRANSPORT_STATE")] [string[]]$RingBufferType, + [int]$CollectionMinutes = 60, [switch]$EnableException ) @@ -129,6 +139,7 @@ function Get-DbaAgRingBuffer { DATEADD(ms, -1 * ($currentTimestamp - [timestamp]), GETDATE()) AS EventTime, record FROM HadrRingBuffer + WHERE DATEADD(ms, -1 * ($currentTimestamp - [timestamp]), GETDATE()) > DATEADD(MINUTE, -$CollectionMinutes, GETDATE()) ORDER BY EventTime DESC;" Write-Message -Level Verbose -Message "Executing SQL Statement: $sql" diff --git a/tests/Get-DbaAgRingBuffer.Tests.ps1 b/tests/Get-DbaAgRingBuffer.Tests.ps1 index 658b5f3c1714..597f105c179c 100644 --- a/tests/Get-DbaAgRingBuffer.Tests.ps1 +++ b/tests/Get-DbaAgRingBuffer.Tests.ps1 @@ -14,6 +14,7 @@ Describe $CommandName -Tag UnitTests { "SqlInstance", "SqlCredential", "RingBufferType", + "CollectionMinutes", "EnableException" ) Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $hasParameters | Should -BeNullOrEmpty