Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 97 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ This style guide provides coding standards for dbatools PowerShell development t
**MODERN RULE**: Do NOT use `Mandatory = $true` or similar boolean attribute assignments. Boolean attributes do not require explicit value assignment in modern PowerShell.

```powershell
# CORRECT - Modern attribute syntax (no = $true)
# CORRECT - Modern attribute syntax (no = $true), Position preserved
param(
[Parameter(Mandatory)]
[Parameter(Mandatory, Position = 0)]
[string]$SqlInstance,

[Parameter(ValueFromPipeline)]
Expand All @@ -26,18 +26,28 @@ param(

# WRONG - Outdated PSv2 syntax (no longer needed)
param(
[Parameter(Mandatory = $true)]
[Parameter(Mandatory = $true, Position = 0)]
[string]$SqlInstance,

[Parameter(ValueFromPipeline = $true)]
[object[]]$InputObject
)

# ALSO WRONG - Removing Position when it existed in original
param(
[Parameter(Mandatory)] # Missing Position = 0 (breaking change!)
[string]$SqlInstance
)
```

**Guidelines:**
- Use `[Parameter(Mandatory)]` not `[Parameter(Mandatory = $true)]`
- Use `[switch]` for boolean flags, not `[bool]` parameters
- Keep non-boolean attributes with values: `[Parameter(ValueFromPipelineByPropertyName = "Name")]`
- **CRITICAL**: Always preserve `Position` attributes when modernizing - removing them is a breaking change
- `[Parameter(Mandatory, Position = 0)]` NOT `[Parameter(Mandatory)]` if Position was present
- Positional parameters allow users to call commands without parameter names
- Example: `Test-DbaAvailabilityGroup $instance` instead of `Test-DbaAvailabilityGroup -SqlInstance $instance`

### POWERSHELL v3 COMPATIBILITY

Expand Down Expand Up @@ -325,6 +335,27 @@ function Set-DbaLogins { }
function New-DbaAgents { }
```

### Microsoft SMO Property Name Typos

**CRITICAL KNOWLEDGE**: Some Microsoft SMO (SQL Server Management Objects) properties contain typos in their official names. These are NOT errors - they are the actual property names you must use.

**Known typos that MUST be preserved:**

1. **`AvailabilityDateabaseId`** (with typo: "Dateabase" instead of "Database")
- Used in: Availability Group DatabaseReplicaStates
- Correct usage: `Where-Object AvailabilityDateabaseId -eq $db.UniqueId`
- DO NOT "fix" this to `AvailabilityDatabaseId` - it will break the code

```powershell
# CORRECT - Uses Microsoft's typo
$databaseReplicaState = $replicaStates | Where-Object AvailabilityDateabaseId -eq $db.UniqueId

# WRONG - "Fixed" spelling will not work
$databaseReplicaState = $replicaStates | Where-Object AvailabilityDatabaseId -eq $db.UniqueId
```

**Important:** When reviewing or modifying code that uses SMO objects, verify the actual property names in Microsoft's documentation or SMO metadata before "correcting" apparent typos. The typo might be intentional (or at least unchangeable) in the SMO library.

### Command Registration

**CRITICAL RULE**: When adding a new command, you MUST register it in TWO places:
Expand Down Expand Up @@ -418,13 +449,17 @@ AfterAll {

### Test Management Guidelines

The dbatools test suite must remain manageable in size while ensuring adequate coverage for important functionality. Follow these guidelines:
The dbatools test suite must remain manageable in size while ensuring adequate coverage for important functionality. Follow these guidelines:

**When to Update Tests:**
- **ALWAYS update parameter validation tests** when parameters are added or removed from a command
- **ADD tests for new functionality** - When adding new parameters or features, include tests that verify the new functionality works correctly
- **ADD regression tests** when fixing a specific bug that needs to be prevented from recurring
- **AVOID bloat** - Don't add generic coverage tests for basic operations unless they test a specific fix or new feature
- **ADD tests for new functionality** - When adding new parameters or features, include tests that verify the new functionality works correctly
- **ADD regression tests** when fixing a specific bug that needs to be prevented from recurring
- **AVOID bloat** - Don't add generic coverage tests for basic operations unless they test a specific fix or new feature

**Parameter Validation Updates:**

Expand Down Expand Up @@ -478,12 +513,46 @@ Context "Force parameter" {
}
```

**Tests for New Features:**

When adding new parameters or functionality, include tests that verify the new feature works:

```powershell
# GOOD - Test for a new parameter that filters results
Context "Filter by recovery model" {
It "Should return only databases with Full recovery model" {
$splatFilter = @{
SqlInstance = $instance
RecoveryModel = "Full"
}
$result = Get-DbaDatabase @splatFilter
$result.RecoveryModel | Should -All -Be "Full"
}
}

# GOOD - Test for a new switch parameter
Context "Force parameter" {
It "Should skip confirmation when -Force is used" {
$splatForce = @{
SqlInstance = $instance
Database = $testDb
Force = $true
Confirm = $false
}
{ Remove-DbaDatabase @splatForce } | Should -Not -Throw
}
}
```

**Regression Tests:**

Add regression tests when fixing bugs:
- Fixing a specific, reproducible bug that should be prevented from recurring
Add regression tests when fixing bugs:
- Fixing a specific, reproducible bug that should be prevented from recurring
- The bug is significant enough to warrant long-term protection
- The test demonstrates the bug is fixed and prevents regression
- The test demonstrates the bug is fixed and prevents regression

Example of when to add a regression test:

Expand All @@ -508,15 +577,21 @@ Context "Regression tests" {

```powershell
# WRONG - Adding general coverage tests for existing functionality without a fix
# WRONG - Adding general coverage tests for existing functionality without a fix
It "Should return correct number of databases" { }
It "Should handle empty result sets" { }
It "Should work with pipeline input" { }

# WRONG - Generic edge case tests unrelated to changes
It "Should handle null parameters gracefully" { }
It "Should work with special characters in names" { }

# WRONG - Generic edge case tests unrelated to changes
It "Should handle null parameters gracefully" { }
It "Should work with special characters in names" { }
```

Don't add tests for existing functionality unless you're fixing a bug or adding a new feature that needs verification.
Don't add tests for existing functionality unless you're fixing a bug or adding a new feature that needs verification.

## VERIFICATION CHECKLIST
Expand All @@ -526,6 +601,7 @@ Don't add tests for existing functionality unless you're fixing a bug or adding
- [ ] Parameter names match original exactly without modification
- [ ] No backticks used for line continuation
- [ ] No `= $true` used in parameter attributes (use modern syntax)
- [ ] Position attributes preserved when modernizing parameters (critical for backward compatibility)
- [ ] Splats used only for 3+ parameters

**Version Compatibility:**
Expand All @@ -547,11 +623,15 @@ Don't add tests for existing functionality unless you're fixing a bug or adding
- [ ] Where-Object conversions applied appropriately
- [ ] Temporary resource cleanup implemented properly
- [ ] Splat usage follows 3+ parameter rule strictly
- [ ] Microsoft SMO property typos preserved (e.g., AvailabilityDateabaseId)

**Command Registration (if adding new commands):**
- [ ] Command name uses singular nouns (not plural)
- [ ] Command uses approved PowerShell verb
- [ ] Command follows `<Verb>-Dba<Noun>` naming pattern
- [ ] Command name uses singular nouns (not plural)
- [ ] Command uses approved PowerShell verb
- [ ] Command follows `<Verb>-Dba<Noun>` naming pattern
- [ ] Command added to `FunctionsToExport` in dbatools.psd1
- [ ] Command added to `Export-ModuleMember` in dbatools.psm1

Expand All @@ -561,17 +641,24 @@ Don't add tests for existing functionality unless you're fixing a bug or adding
- [ ] Regression tests added for significant bug fixes
- [ ] Generic coverage tests avoided unless testing a specific fix or new feature
- [ ] Test suite remains manageable and focused
- [ ] Tests added for new functionality and parameters (not just bloat)
- [ ] Regression tests added for significant bug fixes
- [ ] Generic coverage tests avoided unless testing a specific fix or new feature
- [ ] Test suite remains manageable and focused

## SUMMARY

The golden rules for dbatools code:

1. **NEVER use backticks** - Use splats for 3+ parameters, direct syntax for 1-2
2. **NEVER use `= $true` in parameter attributes** - Use modern syntax: `[Parameter(Mandatory)]` not `[Parameter(Mandatory = $true)]`
3. **NEVER use `::new()` syntax** - Use `New-Object` for PowerShell v3 compatibility
4. **ALWAYS align hashtables** - Equals signs must line up vertically
5. **ALWAYS preserve comments** - Every comment stays exactly as written
6. **ALWAYS use double quotes** - SQL Server module standard
7. **ALWAYS use unique variable names** - Prevent scope collisions
8. **ALWAYS use descriptive splatnames** - `$splatConnection`, not `$splat`
9. **ALWAYS register new commands** - Add to both dbatools.psd1 and dbatools.psm1
3. **NEVER remove Position attributes** - Preserve `Position = 0` when modernizing parameters to avoid breaking changes
4. **NEVER use `::new()` syntax** - Use `New-Object` for PowerShell v3 compatibility
5. **NEVER "fix" Microsoft SMO typos** - Properties like `AvailabilityDateabaseId` are correct as-is
6. **ALWAYS align hashtables** - Equals signs must line up vertically
7. **ALWAYS preserve comments** - Every comment stays exactly as written
8. **ALWAYS use double quotes** - SQL Server module standard
9. **ALWAYS use unique variable names** - Prevent scope collisions
10. **ALWAYS use descriptive splatnames** - `$splatConnection`, not `$splat`
11. **ALWAYS register new commands** - Add to both dbatools.psd1 and dbatools.psm1
12. **ALWAYS use singular nouns** - Command names use singular, not plural
83 changes: 72 additions & 11 deletions public/Test-DbaAvailabilityGroup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ function Test-DbaAvailabilityGroup {
Validates that the most recent database backup chain can be used for AG database addition.
Enables validation using existing backups instead of creating new ones, but requires the last backup to be a transaction log backup. Use this to test AG readiness with your current backup strategy.

.PARAMETER HealthCheck
Performs comprehensive health monitoring of the Availability Group similar to SSMS AG Dashboard.
Returns detailed replica and database synchronization status including queue sizes, LSN information, and performance metrics. Use this to monitor AG health, identify synchronization issues, or troubleshoot failover readiness.

.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.
Expand All @@ -73,21 +77,27 @@ function Test-DbaAvailabilityGroup {
PS C:\> Test-DbaAvailabilityGroup -SqlInstance SQL2016 -AvailabilityGroup TestAG1 -AddDatabase AdventureWorks -SeedingMode Automatic

Test if database AdventureWorks can be added to the Availability Group TestAG1 with automatic seeding.

.EXAMPLE
PS C:\> Test-DbaAvailabilityGroup -SqlInstance SQL2016 -AvailabilityGroup TestAG1 -HealthCheck

Performs comprehensive health monitoring of TestAG1, returning detailed synchronization status for all replicas and databases similar to SSMS AG Dashboard.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100 percent sure, but this might be a breaking change. I think Position = 0 is needed to be able to use Test-DbaAvailabilityGroup $sqlInstance [...] instead of Test-DbaAvailabilityGroup -SqlInstance $sqlInstance [...]. This is not consistent in our code base.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, ill tell it to keep positional parameters.

@claude pls update claude.md again to say dont mess with the positional params when removing the mandatory=true

Copy link

@claude claude bot Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude finished @potatoqualitee's task —— View job


CLAUDE.md updated with positional parameter guidance ✓

Task List:

  • Read current CLAUDE.md to understand structure
  • Update parameter attributes section to preserve positional parameters
  • Commit and push changes
  • Verify completion

Summary

Successfully updated CLAUDE.md with comprehensive guidance about preserving Position attributes when modernizing parameter syntax.

Changes made:

  1. Added critical guideline in the "PARAMETER ATTRIBUTES" section:

    • Always preserve Position attributes when modernizing from Mandatory = $true to Mandatory
    • Removing Position attributes is a breaking change
    • Positional parameters allow users to call commands without parameter names
  2. Updated code examples to show:

    • ✅ Correct: [Parameter(Mandatory, Position = 0)] (modern syntax, Position preserved)
    • ❌ Wrong: [Parameter(Mandatory = $true, Position = 0)] (outdated PSv2 syntax)
    • ❌ Also wrong: [Parameter(Mandatory)] when Position was in the original (breaking change)
  3. Enhanced verification checklist with new requirement:

    • Added: "Position attributes preserved when modernizing parameters (critical for backward compatibility)"
  4. Updated golden rules (now 12 rules):

The guidance ensures that when modernizing parameter attributes, commands remain backward compatible and users can still use positional syntax (e.g., Test-DbaAvailabilityGroup $instance instead of Test-DbaAvailabilityGroup -SqlInstance $instance).


View commit |

[Parameter(Mandatory)]
[DbaInstanceParameter]$SqlInstance,
[PSCredential]$SqlCredential,
[Parameter(Mandatory = $true)]
[Parameter(Mandatory)]
[string]$AvailabilityGroup,
[DbaInstanceParameter[]]$Secondary,
[PSCredential]$SecondarySqlCredential,
[string[]]$AddDatabase,
[ValidateSet('Automatic', 'Manual')]
[ValidateSet("Automatic", "Manual")]
[string]$SeedingMode,
[string]$SharedPath,
[switch]$UseLastBackup,
[switch]$HealthCheck,
[switch]$EnableException
)
process {
Expand All @@ -110,22 +120,74 @@ function Test-DbaAvailabilityGroup {
return
}

if ($ag.LocalReplicaRole -ne 'Primary') {
if (-not $HealthCheck -and $ag.LocalReplicaRole -ne 'Primary') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if HealthCheck param is passed but it's not Primary, this will not get called.

Should this always be if we are not on the primary replica no matter health check or not?

Stop-Function -Message "LocalReplicaRole of replica $server is not Primary, but $($ag.LocalReplicaRole). Please connect to the current primary replica $($ag.PrimaryReplica)."
return
}

# Test for health of Availability Group

# Later: Get replica and database states like in SSMS dashboard
# Now: Just test for ConnectionState -eq 'Connected'
if ($HealthCheck) {
# Comprehensive health monitoring similar to SSMS AG Dashboard
# Returns detailed database replica state information for all replicas

foreach ($replica in $ag.AvailabilityReplicas) {
$replicaId = $replica.UniqueId
$replicaStates = $ag.DatabaseReplicaStates | Where-Object AvailabilityReplicaId -eq $replicaId

# Note on further development:
# As long as there are no databases in the Availability Group, test for RollupSynchronizationState is not useful
foreach ($db in $ag.AvailabilityDatabases) {
$databaseReplicaState = $replicaStates | Where-Object AvailabilityDateabaseId -eq $db.UniqueId
if ($null -eq $databaseReplicaState) {
continue
}

# The primary replica always has the best information about all the replicas.
# We can maybe also connect to the secondary replicas and test their view of the situation, but then only test the local replica.
[PSCustomObject]@{
ComputerName = $ag.ComputerName
InstanceName = $ag.InstanceName
SqlInstance = $ag.SqlInstance
AvailabilityGroup = $ag.Name
PrimaryReplica = $ag.PrimaryReplica
ReplicaServerName = $databaseReplicaState.AvailabilityReplicaServerName
ReplicaRole = $databaseReplicaState.ReplicaRole
ReplicaAvailabilityMode = $replica.AvailabilityMode
ReplicaFailoverMode = $replica.FailoverMode
ReplicaConnectionState = $replica.ConnectionState
ReplicaJoinState = $replica.JoinState
ReplicaSynchronizationState = $replica.RollupSynchronizationState
DatabaseName = $databaseReplicaState.AvailabilityDatabaseName
SynchronizationState = $databaseReplicaState.SynchronizationState
IsFailoverReady = $databaseReplicaState.IsFailoverReady
IsJoined = $databaseReplicaState.IsJoined
IsSuspended = $databaseReplicaState.IsSuspended
SuspendReason = $databaseReplicaState.SuspendReason
EstimatedRecoveryTime = $databaseReplicaState.EstimatedRecoveryTime
EstimatedDataLoss = $databaseReplicaState.EstimatedDataLoss
SynchronizationPerformance = $databaseReplicaState.SynchronizationPerformance
LogSendQueueSize = $databaseReplicaState.LogSendQueueSize
LogSendRate = $databaseReplicaState.LogSendRate
RedoQueueSize = $databaseReplicaState.RedoQueueSize
RedoRate = $databaseReplicaState.RedoRate
FileStreamSendRate = $databaseReplicaState.FileStreamSendRate
EndOfLogLSN = $databaseReplicaState.EndOfLogLSN
RecoveryLSN = $databaseReplicaState.RecoveryLSN
TruncationLSN = $databaseReplicaState.TruncationLSN
LastCommitLSN = $databaseReplicaState.LastCommitLSN
LastCommitTime = $databaseReplicaState.LastCommitTime
LastHardenedLSN = $databaseReplicaState.LastHardenedLSN
LastHardenedTime = $databaseReplicaState.LastHardenedTime
LastReceivedLSN = $databaseReplicaState.LastReceivedLSN
LastReceivedTime = $databaseReplicaState.LastReceivedTime
LastRedoneLSN = $databaseReplicaState.LastRedoneLSN
LastRedoneTime = $databaseReplicaState.LastRedoneTime
LastSentLSN = $databaseReplicaState.LastSentLSN
LastSentTime = $databaseReplicaState.LastSentTime
}
}
}
return
}

# Basic connectivity test for non-HealthCheck scenarios
$failure = $false
foreach ($replica in $ag.AvailabilityReplicas) {
if ($replica.ConnectionState -ne 'Connected') {
Expand All @@ -138,7 +200,6 @@ function Test-DbaAvailabilityGroup {
return
}


# For now, just output the base information.

if (-not $AddDatabase) {
Expand Down
Loading