Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 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
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ applyTo: "**"
- Unit tests: Add `$env:SqlServerDscCI = $true` in `BeforeAll`, remove in `AfterAll`
- Integration tests:
- Use `Connect-SqlDscDatabaseEngine` for SQL Server DB session, and always with correct CI credentials
- Use `Disconnect-SqlDscDatabaseEngine` after `Connect-SqlDscDatabaseEngine`
- Test config: tests/Integration/Commands/README.md and tests/Integration/Resources/README.md
- Integration test script files must be added to a group within the test stage in ./azure-pipelines.yml.
- Choose the appropriate group number based on the required dependencies
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- SqlServerDsc
- Refactor integration tests for _SQL Server Reporting Services_ and _Power BI_
_Report Server_ ([issue #2431](https://github.com/dsccommunity/SqlServerDsc/issues/2431)).
- `Connect-Sql` create connection and server objects as per documentation.
- `Invoke-SqlDscQuery` remove disconnect as there is not an explicit connect.

## [17.4.0] - 2026-01-19

Expand Down
24 changes: 15 additions & 9 deletions source/Modules/SqlServerDsc.Common/Public/Connect-Sql.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
function Connect-Sql
{
[CmdletBinding(DefaultParameterSetName = 'SqlServer')]
[OutputType([Microsoft.SqlServer.Management.Smo.Server])]
param
(
[Parameter(ParameterSetName = 'SqlServer')]
Expand Down Expand Up @@ -92,8 +93,12 @@ function Connect-Sql
$Encrypt
)

Write-Verbose -Message (Get-Module -Name *sql* -ListAvailable | Out-String) -Verbose

Import-SqlDscPreferredModule

[System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.Location -like '*SqlServer*' } | ForEach-Object { Write-Verbose ("GAC:{0}`t`tVersion:{1}`t`tLocation:{2}" -f $_.GlobalAssemblyCache, $_.ImageRuntimeVersion, $_.Location) -Verbose }

<#
Build the connection string in the format: [protocol:]hostname[\instance][,port]
Examples:
Expand Down Expand Up @@ -127,8 +132,7 @@ function Connect-Sql
$databaseEngineInstance = '{0}:{1}' -f $Protocol, $databaseEngineInstance
}

$sqlServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'
$sqlConnectionContext = $sqlServerObject.ConnectionContext
$sqlConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Common.ServerConnection
$sqlConnectionContext.ServerInstance = $databaseEngineInstance
$sqlConnectionContext.StatementTimeout = $StatementTimeout
$sqlConnectionContext.ConnectTimeout = $StatementTimeout
Expand Down Expand Up @@ -179,14 +183,16 @@ function Connect-Sql
{
$onlineStatus = 'Online'
$connectTimer = [System.Diagnostics.StopWatch]::StartNew()
$sqlConnectionContext.Connect()
$sqlServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server -ArgumentList $sqlConnectionContext

<#
The addition of the ConnectTimeout property to the ConnectionContext will force the
Connect() method to block until successful. THe SMO object's Status property may not
report 'Online' immediately even though the Connect() was successful. The loop is to
ensure the SMO's Status property was been updated.
The addition of the ConnectTimeout property to the ConnectionContext will force the
Connect() method to block until successful. The SMO object's Status property may not
report 'Online' immediately even though the Connect() was successful. The loop is to
ensure the SMO's Status property was been updated.
#>
$sqlServerObject.ConnectionContext.Connect()

$sleepInSeconds = 2
do
{
Expand Down Expand Up @@ -270,12 +276,12 @@ function Connect-Sql
{
$connectTimer.Stop()
<#
Connect will ensure we actually can connect, but we need to disconnect
Connect() will ensure we actually can connect, but we need to disconnect
from the session so we don't have anything hanging. If we need run a
method on the returned $sqlServerObject it will automatically open a
new session and then close, therefore we don't need to keep this
session open.
#>
$sqlConnectionContext.Disconnect()
$sqlServerObject.ConnectionContext.Disconnect()
Comment on lines 273 to +281
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against null objects in finally
If Server construction or Connect() throws, Line 281 can throw on $null, masking the original failure. Add null guards (and for $connectTimer) before calling members.

🐛 Proposed fix
 finally
 {
-    $connectTimer.Stop()
+    if ($null -ne $connectTimer)
+    {
+        $connectTimer.Stop()
+    }
     <#
         Connect() will ensure we actually can connect, but we need to disconnect
         from the session so we don't have anything hanging. If we need run a
         method on the returned $sqlServerObject it will automatically open a
         new session and then close, therefore we don't need to keep this
         session open.
     #>
-    $sqlServerObject.ConnectionContext.Disconnect()
+    if ($null -ne $sqlServerObject -and $null -ne $sqlServerObject.ConnectionContext)
+    {
+        $sqlServerObject.ConnectionContext.Disconnect()
+    }
 }
🤖 Prompt for AI Agents
In `@source/Modules/SqlServerDsc.Common/Public/Connect-Sql.ps1` around lines 273 -
281, The finally block may call members on null objects and mask the original
exception; update the Connect-Sql.ps1 finally to null-check $connectTimer and
$sqlServerObject before invoking $connectTimer.Stop() and
$sqlServerObject.ConnectionContext.Disconnect() (e.g., only call Stop() if
$connectTimer -ne $null and only call ConnectionContext.Disconnect() if
$sqlServerObject -ne $null and $sqlServerObject.ConnectionContext -ne $null),
preserving existing behavior but avoiding secondary NREs from Connect()
failures.

}
}
8 changes: 0 additions & 8 deletions source/Public/Invoke-SqlDscQuery.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,4 @@ function Invoke-SqlDscQuery
}
}
}

end
{
if ($PSCmdlet.ParameterSetName -eq 'ByServerName')
{
$ServerObject | Disconnect-SqlDscDatabaseEngine -Force -Verbose:$VerbosePreference
}
}
}
214 changes: 107 additions & 107 deletions tests/Unit/DSC_SqlAGDatabase.Tests.ps1

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions tests/Unit/Public/Disconnect-SqlDscDatabaseEngine.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ AfterAll {
Describe 'Disconnect-SqlDscDatabaseEngine' -Tag 'Public' {
It 'Should have the correct parameters in parameter set <MockParameterSetName>' -ForEach @(
@{
MockParameterSetName = '__AllParameterSets'
MockParameterSetName = '__AllParameterSets'
# cSpell: disable-next
MockExpectedParameters = '[-ServerObject] <Server> [-Force] [-WhatIf] [-Confirm] [<CommonParameters>]'
}
Expand All @@ -63,11 +63,11 @@ Describe 'Disconnect-SqlDscDatabaseEngine' -Tag 'Public' {
} |
Select-Object -Property @(
@{
Name = 'ParameterSetName'
Name = 'ParameterSetName'
Expression = { $_.Name }
},
@{
Name = 'ParameterListAsString'
Name = 'ParameterListAsString'
Expression = { $_.ToString() }
}
)
Expand All @@ -81,7 +81,7 @@ Describe 'Disconnect-SqlDscDatabaseEngine' -Tag 'Public' {
$mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'
$mockServerObject.InstanceName = 'MockInstance'

$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerConnection' |
$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Common.ServerConnection' |
Add-Member -MemberType 'ScriptMethod' -Name 'Disconnect' -Value {
$script:mockMethodDisconnectCallCount += 1
} -PassThru -Force
Expand Down
9 changes: 3 additions & 6 deletions tests/Unit/Public/Invoke-SqlDscQuery.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Describe 'Invoke-SqlDscQuery' -Tag 'Public' {
'MockDatabase' = $databaseObject
} -PassThru -Force

$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerConnection' |
$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Common.ServerConnection' |
Add-Member -MemberType 'NoteProperty' -Name 'StatementTimeout' -Value 100 -PassThru -Force

$mockServerObject.ConnectionContext = $mockConnectionContext
Expand Down Expand Up @@ -186,7 +186,6 @@ Describe 'Invoke-SqlDscQuery' -Tag 'Public' {
$mockSqlCredentialSecurePassword = ConvertTo-SecureString -String $mockSqlCredentialPassword -AsPlainText -Force
$mockSqlCredential = [System.Management.Automation.PSCredential]::new($mockSqlCredentialUserName, $mockSqlCredentialSecurePassword)

Mock -CommandName Disconnect-SqlDscDatabaseEngine
Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith {
return $mockServerObject
}
Expand All @@ -200,7 +199,6 @@ Describe 'Invoke-SqlDscQuery' -Tag 'Public' {
$mockMethodExecuteNonQueryCallCount | Should -Be 1

Should -Invoke -CommandName Connect-SqlDscDatabaseEngine -Exactly -Times 1 -Scope It
Should -Invoke -CommandName Disconnect-SqlDscDatabaseEngine -Exactly -Times 1 -Scope It
}

Context 'When calling the command with optional parameter Encrypt' {
Expand All @@ -212,7 +210,6 @@ Describe 'Invoke-SqlDscQuery' -Tag 'Public' {
$mockMethodExecuteNonQueryCallCount | Should -Be 1

Should -Invoke -CommandName Connect-SqlDscDatabaseEngine -Exactly -Times 1 -Scope It
Should -Invoke -CommandName Disconnect-SqlDscDatabaseEngine -Exactly -Times 1 -Scope It
}
}
}
Expand Down Expand Up @@ -240,7 +237,7 @@ Describe 'Invoke-SqlDscQuery' -Tag 'Public' {
'MockDatabase' = $databaseObject
} -PassThru -Force

$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerConnection' |
$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Common.ServerConnection' |
Add-Member -MemberType 'NoteProperty' -Name 'StatementTimeout' -Value 100 -PassThru -Force

$mockServerObject.ConnectionContext = $mockConnectionContext
Expand Down Expand Up @@ -313,7 +310,7 @@ Describe 'Invoke-SqlDscQuery' -Tag 'Public' {
'MockDatabase' = $databaseObject
} -PassThru -Force

$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerConnection' |
$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Common.ServerConnection' |
Add-Member -MemberType 'NoteProperty' -Name 'StatementTimeout' -Value 100 -PassThru -Force

$mockServerObject.ConnectionContext = $mockConnectionContext
Expand Down
4 changes: 2 additions & 2 deletions tests/Unit/Public/Invoke-SqlDscScalarQuery.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Describe 'Invoke-SqlDscScalarQuery' -Tag 'Public' {

Context 'When executing a scalar query' {
BeforeAll {
$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerConnection'
$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Common.ServerConnection'
$mockConnectionContext.StatementTimeout = 100
$mockConnectionContext | Add-Member -MemberType 'ScriptMethod' -Name 'ExecuteScalar' -Value {
param
Expand Down Expand Up @@ -192,7 +192,7 @@ Describe 'Invoke-SqlDscScalarQuery' -Tag 'Public' {

Context 'When an exception is thrown' {
BeforeAll {
$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.ServerConnection'
$mockConnectionContext = New-Object -TypeName 'Microsoft.SqlServer.Management.Common.ServerConnection'
$mockConnectionContext.StatementTimeout = 100
$mockConnectionContext | Add-Member -MemberType 'ScriptMethod' -Name 'ExecuteScalar' -Value {
$script:mockMethodExecuteScalarCallCount += 1
Expand Down
Loading
Loading