From cd004e1eced1b62ae4114ffba1efa32ed469afd2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 12:00:16 +0000 Subject: [PATCH 01/87] Initial plan From 9bf8d27a3335047e99b2b111e782e15bdb766623 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 12:07:14 +0000 Subject: [PATCH 02/87] Add New-SqlDscDatabaseSnapshot command with tests Co-authored-by: johlju <7189721+johlju@users.noreply.github.com> --- CHANGELOG.md | 4 + source/Public/New-SqlDscDatabaseSnapshot.ps1 | 195 ++++++++++++ source/en-US/SqlServerDsc.strings.psd1 | 4 + .../New-SqlDscDatabaseSnapshot.Tests.ps1 | 292 ++++++++++++++++++ 4 files changed, 495 insertions(+) create mode 100644 source/Public/New-SqlDscDatabaseSnapshot.ps1 create mode 100644 tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 575be63e50..9e269a3bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added public command `New-SqlDscDatabaseSnapshot` to create database snapshots + in a SQL Server Database Engine instance using SMO ([issue #XXXX](https://github.com/dsccommunity/SqlServerDsc/issues/XXXX)). + This command provides an automated and DSC-friendly approach to snapshot management + by leveraging `New-SqlDscDatabase` for the actual creation. - Added public command `Set-SqlDscDatabaseOwner` to change the owner of a SQL Server database [issue #2177](https://github.com/dsccommunity/SqlServerDsc/issues/2177). This command uses the SMO `SetOwner()` method and supports both `ServerObject` diff --git a/source/Public/New-SqlDscDatabaseSnapshot.ps1 b/source/Public/New-SqlDscDatabaseSnapshot.ps1 new file mode 100644 index 0000000000..de117b90b4 --- /dev/null +++ b/source/Public/New-SqlDscDatabaseSnapshot.ps1 @@ -0,0 +1,195 @@ +<# + .SYNOPSIS + Creates a new database snapshot in a SQL Server Database Engine instance. + + .DESCRIPTION + This command creates a new database snapshot in a SQL Server Database Engine + instance using SMO. It provides an automated and DSC-friendly approach to + snapshot management by leveraging the existing `New-SqlDscDatabase` command + for the actual creation. + + .PARAMETER ServerObject + Specifies the current server connection object. This parameter is used in the + ServerObject parameter set. + + .PARAMETER Name + Specifies the name of the database snapshot to be created. + + .PARAMETER DatabaseName + Specifies the name of the source database from which to create a snapshot. + This parameter is used in the ServerObject parameter set. + + .PARAMETER DatabaseObject + Specifies the source database object to snapshot. This parameter can be + provided via pipeline and is used in the DatabaseObject parameter set. + + .PARAMETER Force + Specifies that the snapshot should be created without any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s databases should be refreshed before + creating the snapshot. This is helpful when databases could have been + modified outside of the **ServerObject**, for example through T-SQL. But + on instances with a large amount of databases it might be better to make + sure the **ServerObject** is recent enough. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | New-SqlDscDatabaseSnapshot -Name 'MyDatabase_Snapshot' -DatabaseName 'MyDatabase' + + Creates a new database snapshot named **MyDatabase_Snapshot** from the source + database **MyDatabase**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $databaseObject = $serverObject | Get-SqlDscDatabase -Name 'MyDatabase' + $databaseObject | New-SqlDscDatabaseSnapshot -Name 'MyDatabase_Snapshot' -Force + + Creates a new database snapshot named **MyDatabase_Snapshot** from the database + object **MyDatabase** without prompting for confirmation. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | New-SqlDscDatabaseSnapshot -Name 'MyDB_Snap' -DatabaseName 'MyDatabase' -Force + + Creates a new database snapshot named **MyDB_Snap** from the source database + **MyDatabase** without prompting for confirmation. + + .INPUTS + `[Microsoft.SqlServer.Management.Smo.Server]` + + `[Microsoft.SqlServer.Management.Smo.Database]` + + .OUTPUTS + `[Microsoft.SqlServer.Management.Smo.Database]` + + .NOTES + This command is for snapshot creation only and does not support modification + of existing snapshots. + + Database snapshots are only supported in certain SQL Server editions (Enterprise, + Developer, and Evaluation editions). The command will validate edition support + before attempting to create the snapshot. +#> +function New-SqlDscDatabaseSnapshot +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [OutputType([Microsoft.SqlServer.Management.Smo.Database])] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'DatabaseObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Database] + $DatabaseObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $DatabaseName, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + begin + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + } + + process + { + # Determine the server object and source database name based on parameter set + if ($PSCmdlet.ParameterSetName -eq 'DatabaseObject') + { + $ServerObject = $DatabaseObject.Parent + $DatabaseName = $DatabaseObject.Name + } + + Write-Verbose -Message ($script:localizedData.DatabaseSnapshot_Create -f $Name, $DatabaseName, $ServerObject.InstanceName) + + # Validate SQL Server edition supports snapshots + $supportedEditions = @('Enterprise', 'Developer', 'EnterpriseCore', 'EnterpriseOrDeveloper') + + if ($ServerObject.EngineEdition -notin @('Enterprise', 'EnterpriseEvaluation')) + { + # Check edition name for older servers or evaluation + $editionName = $ServerObject.Edition + + $isSupported = $false + + foreach ($supportedEdition in $supportedEditions) + { + if ($editionName -like "*$supportedEdition*") + { + $isSupported = $true + break + } + } + + # Also check for Evaluation edition + if ($editionName -like '*Evaluation*') + { + $isSupported = $true + } + + if (-not $isSupported) + { + $errorMessage = $script:localizedData.DatabaseSnapshot_EditionNotSupported -f $ServerObject.InstanceName, $editionName + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage), + 'NSDS0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $ServerObject + ) + ) + } + } + + # Create the snapshot using New-SqlDscDatabase + $newSqlDscDatabaseParameters = @{ + ServerObject = $ServerObject + Name = $Name + DatabaseSnapshotBaseName = $DatabaseName + Force = $Force + } + + if ($PSCmdlet.ParameterSetName -eq 'ServerObject' -and $Refresh.IsPresent) + { + $newSqlDscDatabaseParameters['Refresh'] = $true + } + + # Use WhatIf and Confirm from the current cmdlet context + if ($PSBoundParameters.ContainsKey('WhatIf')) + { + $newSqlDscDatabaseParameters['WhatIf'] = $WhatIf + } + + if ($PSBoundParameters.ContainsKey('Confirm')) + { + $newSqlDscDatabaseParameters['Confirm'] = $Confirm + } + + $snapshotDatabaseObject = New-SqlDscDatabase @newSqlDscDatabaseParameters + + return $snapshotDatabaseObject + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index af4fbf7ff8..4c552fa7e3 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -365,6 +365,10 @@ ConvertFrom-StringData @' # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. Database_Create_ShouldProcessCaption = Create database on instance + ## New-SqlDscDatabaseSnapshot + DatabaseSnapshot_Create = Creating database snapshot '{0}' from source database '{1}' on instance '{2}'. (NSDS0002) + DatabaseSnapshot_EditionNotSupported = Database snapshots are not supported on SQL Server instance '{0}' with edition '{1}'. Snapshots are only supported in Enterprise, Developer, and Evaluation editions. (NSDS0001) + ## Set-SqlDscDatabaseProperty Database_Set = Setting properties of database '{0}' on instance '{1}'. (SSDDP0001) Database_Updating = Updating database '{0}'. (SSDDP0002) diff --git a/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 b/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 new file mode 100644 index 0000000000..d37c9b4810 --- /dev/null +++ b/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 @@ -0,0 +1,292 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { + Context 'When creating a database snapshot using ServerObject parameter set' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Enterprise' -Force + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Enterprise Edition' -Force + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'VersionMajor' -Value 15 -Force + + # Mock source database + $mockSourceDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockSourceDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'SourceDatabase' -Force + + $mockServerObject | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'SourceDatabase' = $mockSourceDatabase + } | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation + } -PassThru -Force + } -Force + + Mock -CommandName 'New-SqlDscDatabase' -MockWith { + $mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force + $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'DatabaseSnapshotBaseName' -Value $DatabaseSnapshotBaseName -Force + return $mockSnapshotDatabase + } + } + + It 'Should create a database snapshot successfully with minimal parameters' { + $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -Force + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestSnapshot' + $result.DatabaseSnapshotBaseName | Should -Be 'SourceDatabase' + Should -Invoke -CommandName 'New-SqlDscDatabase' -Exactly -Times 1 + } + + It 'Should pass the correct parameters to New-SqlDscDatabase' { + $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'MySnapshot' -DatabaseName 'SourceDatabase' -Force + + Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { + $ServerObject.InstanceName -eq 'TestInstance' -and + $Name -eq 'MySnapshot' -and + $DatabaseSnapshotBaseName -eq 'SourceDatabase' -and + $Force -eq $true + } -Exactly -Times 1 + } + + It 'Should pass Refresh parameter when specified' { + $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -Refresh -Force + + Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { + $Refresh -eq $true + } -Exactly -Times 1 + } + } + + Context 'When creating a database snapshot using DatabaseObject parameter set' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Enterprise' -Force + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Enterprise Edition' -Force + + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Parent' -Value $mockServerObject -Force + + Mock -CommandName 'New-SqlDscDatabase' -MockWith { + $mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force + $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'DatabaseSnapshotBaseName' -Value $DatabaseSnapshotBaseName -Force + return $mockSnapshotDatabase + } + } + + It 'Should create a database snapshot from DatabaseObject successfully' { + $result = New-SqlDscDatabaseSnapshot -DatabaseObject $mockDatabaseObject -Name 'TestSnapshot' -Force + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestSnapshot' + $result.DatabaseSnapshotBaseName | Should -Be 'MyDatabase' + Should -Invoke -CommandName 'New-SqlDscDatabase' -Exactly -Times 1 + } + + It 'Should pass the correct parameters to New-SqlDscDatabase from DatabaseObject' { + $result = New-SqlDscDatabaseSnapshot -DatabaseObject $mockDatabaseObject -Name 'MySnapshot' -Force + + Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { + $ServerObject.InstanceName -eq 'TestInstance' -and + $Name -eq 'MySnapshot' -and + $DatabaseSnapshotBaseName -eq 'MyDatabase' -and + $Force -eq $true + } -Exactly -Times 1 + } + } + + Context 'When SQL Server edition does not support snapshots' { + BeforeAll { + $mockServerObjectStandard = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObjectStandard | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObjectStandard | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Standard' -Force + $mockServerObjectStandard | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Standard Edition' -Force + + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Parent' -Value $mockServerObjectStandard -Force + + Mock -CommandName 'New-SqlDscDatabase' + } + + It 'Should throw error when SQL Server edition does not support snapshots' { + $errorRecord = { New-SqlDscDatabaseSnapshot -ServerObject $mockServerObjectStandard -Name 'TestSnapshot' -DatabaseName 'MyDatabase' -Force } | + Should -Throw -ExpectedMessage '*not supported*' -PassThru + + $errorRecord.Exception.Message | Should -BeLike '*not supported*' + $errorRecord.FullyQualifiedErrorId | Should -Be 'NSDS0001,New-SqlDscDatabaseSnapshot' + $errorRecord.CategoryInfo.Category | Should -Be 'InvalidOperation' + } + + It 'Should throw error when using DatabaseObject with unsupported edition' { + { New-SqlDscDatabaseSnapshot -DatabaseObject $mockDatabaseObject -Name 'TestSnapshot' -Force } | + Should -Throw -ExpectedMessage '*not supported*' + } + + It 'Should not call New-SqlDscDatabase when edition is not supported' { + { New-SqlDscDatabaseSnapshot -ServerObject $mockServerObjectStandard -Name 'TestSnapshot' -DatabaseName 'MyDatabase' -Force } | + Should -Throw + + Should -Invoke -CommandName 'New-SqlDscDatabase' -Exactly -Times 0 + } + } + + Context 'When SQL Server edition is Developer' { + BeforeAll { + $mockServerObjectDeveloper = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObjectDeveloper | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObjectDeveloper | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Standard' -Force + $mockServerObjectDeveloper | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Developer Edition' -Force + + Mock -CommandName 'New-SqlDscDatabase' -MockWith { + $mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force + $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'DatabaseSnapshotBaseName' -Value $DatabaseSnapshotBaseName -Force + return $mockSnapshotDatabase + } + } + + It 'Should create a database snapshot on Developer edition' { + $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObjectDeveloper -Name 'TestSnapshot' -DatabaseName 'MyDatabase' -Force + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestSnapshot' + Should -Invoke -CommandName 'New-SqlDscDatabase' -Exactly -Times 1 + } + } + + Context 'When SQL Server edition is Evaluation' { + BeforeAll { + $mockServerObjectEvaluation = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObjectEvaluation | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObjectEvaluation | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Standard' -Force + $mockServerObjectEvaluation | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Evaluation Edition' -Force + + Mock -CommandName 'New-SqlDscDatabase' -MockWith { + $mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force + $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'DatabaseSnapshotBaseName' -Value $DatabaseSnapshotBaseName -Force + return $mockSnapshotDatabase + } + } + + It 'Should create a database snapshot on Evaluation edition' { + $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObjectEvaluation -Name 'TestSnapshot' -DatabaseName 'MyDatabase' -Force + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestSnapshot' + Should -Invoke -CommandName 'New-SqlDscDatabase' -Exactly -Times 1 + } + } + + Context 'Parameter validation' { + It 'Should have the correct parameters in parameter set ServerObject' -ForEach @( + @{ + ExpectedParameterSetName = 'ServerObject' + ExpectedParameters = '-ServerObject -Name -DatabaseName [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'New-SqlDscDatabaseSnapshot').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + + It 'Should have the correct parameters in parameter set DatabaseObject' -ForEach @( + @{ + ExpectedParameterSetName = 'DatabaseObject' + ExpectedParameters = '-DatabaseObject -Name [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'New-SqlDscDatabaseSnapshot').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + + It 'Should have ServerObject as a mandatory parameter in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDatabaseSnapshot').Parameters['ServerObject'] + $serverObjectSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'ServerObject' } + $serverObjectSetAttribute.Mandatory | Should -BeTrue + } + + It 'Should have DatabaseObject as a mandatory parameter in DatabaseObject parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDatabaseSnapshot').Parameters['DatabaseObject'] + $databaseObjectSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'DatabaseObject' } + $databaseObjectSetAttribute.Mandatory | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDatabaseSnapshot').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have DatabaseName as a mandatory parameter in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDatabaseSnapshot').Parameters['DatabaseName'] + $serverObjectSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'ServerObject' } + $serverObjectSetAttribute.Mandatory | Should -BeTrue + } + } +} From b4e92fd9b36a9ed28c92d93f2efcaf4473026793 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 12:15:02 +0000 Subject: [PATCH 03/87] Update CHANGELOG to remove placeholder issue reference Co-authored-by: johlju <7189721+johlju@users.noreply.github.com> --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e269a3bbd..b85283507a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,9 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added public command `New-SqlDscDatabaseSnapshot` to create database snapshots - in a SQL Server Database Engine instance using SMO ([issue #XXXX](https://github.com/dsccommunity/SqlServerDsc/issues/XXXX)). - This command provides an automated and DSC-friendly approach to snapshot management - by leveraging `New-SqlDscDatabase` for the actual creation. + in a SQL Server Database Engine instance using SMO. This command provides an + automated and DSC-friendly approach to snapshot management by leveraging + `New-SqlDscDatabase` for the actual creation. - Added public command `Set-SqlDscDatabaseOwner` to change the owner of a SQL Server database [issue #2177](https://github.com/dsccommunity/SqlServerDsc/issues/2177). This command uses the SMO `SetOwner()` method and supports both `ServerObject` From b0b1ad5e45da5347e6ae9a077bad868e7fdedaa6 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 15:59:00 +0100 Subject: [PATCH 04/87] Add integration tests for New-SqlDscFileGroup and New-SqlDscDataFile commands --- azure-pipelines.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f88596af6b..f7e20284df 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -293,6 +293,10 @@ stages: # Group 2 'tests/Integration/Commands/PostInstallationConfiguration.Integration.Tests.ps1' # Group 3 + 'tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1' + 'tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1' + 'tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1' + 'tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscSetupLog.Integration.Tests.ps1' 'tests/Integration/Commands/Connect-SqlDscDatabaseEngine.Integration.Tests.ps1' 'tests/Integration/Commands/Disconnect-SqlDscDatabaseEngine.Integration.Tests.ps1' From 18364c102661caa4acab4c0432e4dc64217bc8ef Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 15:59:28 +0100 Subject: [PATCH 05/87] Add Add-SqlDscDataFile command with integration and unit tests --- source/Public/Add-SqlDscDataFile.ps1 | 67 ++++++++++ .../Add-SqlDscDataFile.Integration.Tests.ps1 | 117 +++++++++++++++++ .../Unit/Public/Add-SqlDscDataFile.Tests.ps1 | 118 ++++++++++++++++++ 3 files changed, 302 insertions(+) create mode 100644 source/Public/Add-SqlDscDataFile.ps1 create mode 100644 tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 create mode 100644 tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 diff --git a/source/Public/Add-SqlDscDataFile.ps1 b/source/Public/Add-SqlDscDataFile.ps1 new file mode 100644 index 0000000000..1413cb5bf4 --- /dev/null +++ b/source/Public/Add-SqlDscDataFile.ps1 @@ -0,0 +1,67 @@ +<# + .SYNOPSIS + Adds a DataFile to a SQL Server FileGroup. + + .DESCRIPTION + This command adds an existing DataFile object to a FileGroup. The DataFile + must be created first using New-SqlDscDataFile or by other means. + + .PARAMETER FileGroup + Specifies the FileGroup object to which the DataFile will be added. + + .PARAMETER DataFile + Specifies the DataFile object to add to the FileGroup. + + .PARAMETER PassThru + Returns the DataFile object that was added to the FileGroup. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $database = $serverObject.Databases['MyDatabase'] + $fileGroup = $database.FileGroups['PRIMARY'] + $dataFile = New-SqlDscDataFile -Name 'MyDatabase_Data' -FileName 'C:\Data\MyDatabase_Data.mdf' + Add-SqlDscDataFile -FileGroup $fileGroup -DataFile $dataFile + + Creates a DataFile and adds it to the PRIMARY FileGroup. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $database = $serverObject.Databases['MyDatabase'] + $fileGroup = $database.FileGroups['PRIMARY'] + $dataFile = New-SqlDscDataFile -Name 'MyDatabase_Data' -FileName 'C:\Data\MyDatabase_Data.mdf' | + Add-SqlDscDataFile -FileGroup $fileGroup -PassThru + + Creates a DataFile, adds it to the PRIMARY FileGroup, and returns the DataFile object using pipeline input. + + .OUTPUTS + None. Unless -PassThru is specified, in which case it returns `[Microsoft.SqlServer.Management.Smo.DataFile]`. +#> +function Add-SqlDscDataFile +{ + [CmdletBinding()] + [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.FileGroup] + $FileGroup, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.DataFile] + $DataFile, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru + ) + + process + { + $FileGroup.Files.Add($DataFile) + + if ($PassThru.IsPresent) + { + return $DataFile + } + } +} diff --git a/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 new file mode 100644 index 0000000000..4a084222e1 --- /dev/null +++ b/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 @@ -0,0 +1,117 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Import the SMO module to ensure real SMO types are available + Import-SqlDscPreferredModule +} + +Describe 'Add-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + Context 'When adding a DataFile to a FileGroup with real SMO types' { + BeforeEach { + # Create real SMO objects + $script:testFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() + $script:testFileGroup.Name = 'TestFileGroup' + $script:testDataFile = [Microsoft.SqlServer.Management.Smo.DataFile]::new() + $script:testDataFile.Name = 'TestDataFile' + } + + It 'Should add a DataFile to FileGroup successfully' { + $initialCount = $script:testFileGroup.Files.Count + + Add-SqlDscDataFile -FileGroup $script:testFileGroup -DataFile $script:testDataFile + + $script:testFileGroup.Files.Count | Should -Be ($initialCount + 1) + $script:testFileGroup.Files[$script:testDataFile.Name] | Should -Be $script:testDataFile + } + + It 'Should return DataFile when using PassThru' { + $result = Add-SqlDscDataFile -FileGroup $script:testFileGroup -DataFile $script:testDataFile -PassThru + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result | Should -Be $script:testDataFile + } + + It 'Should accept DataFile from pipeline' { + $initialCount = $script:testFileGroup.Files.Count + + $script:testDataFile | Add-SqlDscDataFile -FileGroup $script:testFileGroup + + $script:testFileGroup.Files.Count | Should -Be ($initialCount + 1) + } + + It 'Should add multiple DataFiles to FileGroup' { + $dataFile1 = [Microsoft.SqlServer.Management.Smo.DataFile]::new() + $dataFile1.Name = 'DataFile1' + $dataFile2 = [Microsoft.SqlServer.Management.Smo.DataFile]::new() + $dataFile2.Name = 'DataFile2' + + $initialCount = $script:testFileGroup.Files.Count + + Add-SqlDscDataFile -FileGroup $script:testFileGroup -DataFile @($dataFile1, $dataFile2) + + $script:testFileGroup.Files.Count | Should -Be ($initialCount + 2) + $script:testFileGroup.Files[$dataFile1.Name] | Should -Be $dataFile1 + $script:testFileGroup.Files[$dataFile2.Name] | Should -Be $dataFile2 + } + + It 'Should add multiple DataFiles via pipeline and return them with PassThru' { + $dataFile1 = [Microsoft.SqlServer.Management.Smo.DataFile]::new() + $dataFile1.Name = 'DataFile1' + $dataFile2 = [Microsoft.SqlServer.Management.Smo.DataFile]::new() + $dataFile2.Name = 'DataFile2' + + $result = @($dataFile1, $dataFile2) | Add-SqlDscDataFile -FileGroup $script:testFileGroup -PassThru + + $result | Should -HaveCount 2 + $result[0] | Should -Be $dataFile1 + $result[1] | Should -Be $dataFile2 + } + } + + Context 'When verifying DataFile parent relationship' { + BeforeEach { + $script:testFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() + $script:testFileGroup.Name = 'TestFileGroup' + $script:testDataFile = [Microsoft.SqlServer.Management.Smo.DataFile]::new() + $script:testDataFile.Name = 'TestDataFile' + } + + It 'Should update DataFile parent reference when added to FileGroup' { + $script:testDataFile.Parent | Should -BeNullOrEmpty + + Add-SqlDscDataFile -FileGroup $script:testFileGroup -DataFile $script:testDataFile + + # Note: The parent may or may not be updated depending on SMO implementation + # This test verifies the DataFile is in the collection + $script:testFileGroup.Files[$script:testDataFile.Name] | Should -Be $script:testDataFile + } + } +} diff --git a/tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 new file mode 100644 index 0000000000..8f12f3e5b7 --- /dev/null +++ b/tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 @@ -0,0 +1,118 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Add-SqlDscDataFile' -Tag 'Public' { + Context 'When adding a DataFile to a FileGroup' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' -ArgumentList @($mockServerObject, 'MyDatabase') + $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'PRIMARY') + $mockDataFileObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject, 'MyDataFile', 'C:\Data\MyDataFile.mdf') + } + + It 'Should add the DataFile to the FileGroup without returning output' { + { Add-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFile $mockDataFileObject } | Should -Not -Throw + + $mockFileGroupObject.Files.Count | Should -Be 1 + $mockFileGroupObject.Files[0].Name | Should -Be 'MyDataFile' + $mockFileGroupObject.Files[0].FileName | Should -Be 'C:\Data\MyDataFile.mdf' + } + + It 'Should add the DataFile to the FileGroup and return the DataFile when PassThru is specified' { + $mockFileGroupObject2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'SECONDARY') + $mockDataFileObject2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject2, 'MyDataFile2', 'C:\Data\MyDataFile2.ndf') + + $result = Add-SqlDscDataFile -FileGroup $mockFileGroupObject2 -DataFile $mockDataFileObject2 -PassThru + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'MyDataFile2' + $result.FileName | Should -Be 'C:\Data\MyDataFile2.ndf' + $mockFileGroupObject2.Files.Count | Should -Be 1 + } + + It 'Should accept DataFile from pipeline' { + $mockFileGroupObject3 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'TERTIARY') + $mockDataFileObject3 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject3, 'MyDataFile3', 'C:\Data\MyDataFile3.ndf') + + { $mockDataFileObject3 | Add-SqlDscDataFile -FileGroup $mockFileGroupObject3 } | Should -Not -Throw + + $mockFileGroupObject3.Files.Count | Should -Be 1 + $mockFileGroupObject3.Files[0].Name | Should -Be 'MyDataFile3' + } + } + + Context 'Parameter validation' { + BeforeAll { + $commandInfo = Get-Command -Name 'Add-SqlDscDataFile' + } + + It 'Should have FileGroup as a mandatory parameter' { + $parameterInfo = $commandInfo.Parameters['FileGroup'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have DataFile as a mandatory parameter' { + $parameterInfo = $commandInfo.Parameters['DataFile'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have PassThru as an optional parameter' { + $parameterInfo = $commandInfo.Parameters['PassThru'] + $parameterInfo.Attributes.Mandatory | Should -Not -Contain $true + } + + It 'Should have DataFile parameter accept pipeline input' { + $parameterInfo = $commandInfo.Parameters['DataFile'] + $parameterInfo.Attributes.ValueFromPipeline | Should -Contain $true + } + } +} From d5a571d754bb4c1ce7afdb7bbf9355e1b5b3a1f0 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 15:59:32 +0100 Subject: [PATCH 06/87] Add FileGroup and DataFile classes with collections to manage database file groups and data files --- tests/Unit/Stubs/SMO.cs | 138 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 134 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index e78f98c061..bd6f720221 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -877,21 +877,29 @@ public class Database public DateTime CreateDate; public DatabaseEncryptionKey DatabaseEncryptionKey; public DateTime LastBackupDate = DateTime.Now; - public Hashtable FileGroups; + public FileGroupCollection FileGroups { get; set; } public Hashtable LogFiles; + public string DatabaseSnapshotBaseName; - public Database( Server server, string name ) { + public Database( Server server, string name ) + { this.Name = name; this.Parent = server; + this.FileGroups = new FileGroupCollection(); } - public Database( Object server, string name ) { + public Database( Object server, string name ) + { this.Name = name; this.Parent = (Server)server; + this.FileGroups = new FileGroupCollection(); } - public Database() {} + public Database() + { + this.FileGroups = new FileGroupCollection(); + } public string Name; public Server Parent; @@ -961,6 +969,128 @@ public void SetDefaultFullTextCatalog( string catalogName ) } } + // TypeName: Microsoft.SqlServer.Management.Smo.FileGroup + // Used by: + // New-SqlDscDatabase.Tests.ps1 + // New-SqlDscDatabaseSnapshot.Tests.ps1 + public class FileGroup + { + public FileGroup() + { + this.Files = new DataFileCollection(); + } + + public FileGroup(Database database) + { + this.Parent = database; + this.Files = new DataFileCollection(); + } + + public FileGroup(Database database, string name) + { + this.Parent = database; + this.Name = name; + this.Files = new DataFileCollection(); + } + + public string Name { get; set; } + public Database Parent { get; set; } + public DataFileCollection Files { get; set; } + + public void Create() + { + } + } + + // TypeName: Microsoft.SqlServer.Management.Smo.FileGroupCollection + // Used by: + // New-SqlDscDatabase.Tests.ps1 + // New-SqlDscDatabaseSnapshot.Tests.ps1 + public class FileGroupCollection : Collection + { + public FileGroup this[string name] + { + get + { + foreach (FileGroup fileGroup in this) + { + if (name == fileGroup.Name) + { + return fileGroup; + } + } + + return null; + } + } + + new public void Add(FileGroup fileGroup) + { + base.Add(fileGroup); + } + } + + // TypeName: Microsoft.SqlServer.Management.Smo.DataFile + // Used by: + // New-SqlDscDatabase.Tests.ps1 + // New-SqlDscDatabaseSnapshot.Tests.ps1 + // New-SqlDscDataFile.Tests.ps1 + public class DataFile + { + public DataFile() + { + } + + public DataFile(FileGroup fileGroup, string name) + { + this.Parent = fileGroup; + this.Name = name; + } + + public DataFile(FileGroup fileGroup, string name, string fileName) + { + this.Parent = fileGroup; + this.Name = name; + this.FileName = fileName; + } + + public string Name { get; set; } + public string FileName { get; set; } + public FileGroup Parent { get; set; } + + public void Create() + { + } + } + + // TypeName: Microsoft.SqlServer.Management.Smo.DataFileCollection + // Used by: + // New-SqlDscDatabase.Tests.ps1 + // New-SqlDscDatabaseSnapshot.Tests.ps1 + public class DataFileCollection : Collection + { + public DataFile this[string name] + { + get + { + foreach (DataFile dataFile in this) + { + if (name == dataFile.Name) + { + return dataFile; + } + } + + return null; + } + } + + new public void Add(DataFile dataFile) + { + base.Add(dataFile); + } + } + // TypeName: Microsoft.SqlServer.Management.Smo.User // BaseType: Microsoft.SqlServer.Management.Smo.ScriptNameObjectBase // Used by: From 58d605df83433626e96a4f2a30927463233a92da Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 15:59:41 +0100 Subject: [PATCH 07/87] Add Add-SqlDscFileGroup command and associated tests for managing FileGroups in a Database --- source/Public/Add-SqlDscFileGroup.ps1 | 64 ++++++++ .../Add-SqlDscFileGroup.Integration.Tests.ps1 | 145 +++++++++++++++++ .../Unit/Public/Add-SqlDscFileGroup.Tests.ps1 | 149 ++++++++++++++++++ 3 files changed, 358 insertions(+) create mode 100644 source/Public/Add-SqlDscFileGroup.ps1 create mode 100644 tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 create mode 100644 tests/Unit/Public/Add-SqlDscFileGroup.Tests.ps1 diff --git a/source/Public/Add-SqlDscFileGroup.ps1 b/source/Public/Add-SqlDscFileGroup.ps1 new file mode 100644 index 0000000000..0100cf503f --- /dev/null +++ b/source/Public/Add-SqlDscFileGroup.ps1 @@ -0,0 +1,64 @@ +<# + .SYNOPSIS + Adds one or more FileGroup objects to a Database object. + + .DESCRIPTION + This command adds one or more FileGroup objects to a Database object's FileGroups + collection. This is useful when you have created FileGroup objects using + New-SqlDscFileGroup and want to associate them with a Database. + + .PARAMETER Database + Specifies the Database object to which the FileGroups will be added. + + .PARAMETER FileGroup + Specifies one or more FileGroup objects to add to the Database. This parameter + accepts pipeline input. + + .PARAMETER PassThru + Returns the FileGroup objects that were added to the Database. + + .OUTPUTS + None, or [Microsoft.SqlServer.Management.Smo.FileGroup[]] if PassThru is specified. + + .EXAMPLE + Add-SqlDscFileGroup -Database $database -FileGroup $fileGroup + + Adds a single FileGroup to the Database. + + .EXAMPLE + $fileGroups | Add-SqlDscFileGroup -Database $database -PassThru + + Adds multiple FileGroups to the Database via pipeline and returns the FileGroup objects. +#> +function Add-SqlDscFileGroup +{ + [CmdletBinding()] + [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup[]])] + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Database] + $Database, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.FileGroup[]] + $FileGroup, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru + ) + + process + { + foreach ($fileGroupObject in $FileGroup) + { + $Database.FileGroups.Add($fileGroupObject) + + if ($PassThru.IsPresent) + { + $fileGroupObject + } + } + } +} diff --git a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 new file mode 100644 index 0000000000..6a8f700379 --- /dev/null +++ b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 @@ -0,0 +1,145 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Import the SMO module to ensure real SMO types are available + Import-SqlDscPreferredModule +} + +Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + Context 'When adding a FileGroup to a Database with real SMO types' { + BeforeEach { + # Create real SMO objects + $script:testDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() + $script:testDatabase.Name = 'TestDatabase' + $script:testFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() + $script:testFileGroup.Name = 'TestFileGroup' + } + + It 'Should add a FileGroup to Database successfully' { + $initialCount = $script:testDatabase.FileGroups.Count + + Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $script:testFileGroup + + $script:testDatabase.FileGroups.Count | Should -Be ($initialCount + 1) + $script:testDatabase.FileGroups[$script:testFileGroup.Name] | Should -Be $script:testFileGroup + } + + It 'Should return FileGroup when using PassThru' { + $result = Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $script:testFileGroup -PassThru + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result | Should -Be $script:testFileGroup + } + + It 'Should accept FileGroup from pipeline' { + $initialCount = $script:testDatabase.FileGroups.Count + + $script:testFileGroup | Add-SqlDscFileGroup -Database $script:testDatabase + + $script:testDatabase.FileGroups.Count | Should -Be ($initialCount + 1) + } + + It 'Should add multiple FileGroups to Database' { + $fileGroup1 = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() + $fileGroup1.Name = 'FileGroup1' + $fileGroup2 = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() + $fileGroup2.Name = 'FileGroup2' + + $initialCount = $script:testDatabase.FileGroups.Count + + Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup @($fileGroup1, $fileGroup2) + + $script:testDatabase.FileGroups.Count | Should -Be ($initialCount + 2) + $script:testDatabase.FileGroups[$fileGroup1.Name] | Should -Be $fileGroup1 + $script:testDatabase.FileGroups[$fileGroup2.Name] | Should -Be $fileGroup2 + } + + It 'Should add multiple FileGroups via pipeline and return them with PassThru' { + $fileGroup1 = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() + $fileGroup1.Name = 'FileGroup1' + $fileGroup2 = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() + $fileGroup2.Name = 'FileGroup2' + + $result = @($fileGroup1, $fileGroup2) | Add-SqlDscFileGroup -Database $script:testDatabase -PassThru + + $result | Should -HaveCount 2 + $result[0] | Should -Be $fileGroup1 + $result[1] | Should -Be $fileGroup2 + } + } + + Context 'When verifying FileGroup parent relationship' { + BeforeEach { + $script:testDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() + $script:testDatabase.Name = 'TestDatabase' + $script:testFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() + $script:testFileGroup.Name = 'TestFileGroup' + } + + It 'Should update FileGroup parent reference when added to Database' { + $script:testFileGroup.Parent | Should -BeNullOrEmpty + + Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $script:testFileGroup + + # Note: The parent may or may not be updated depending on SMO implementation + # This test verifies the FileGroup is in the collection + $script:testDatabase.FileGroups[$script:testFileGroup.Name] | Should -Be $script:testFileGroup + } + } + + Context 'When integrating FileGroup and DataFile creation' { + BeforeEach { + $script:testDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() + $script:testDatabase.Name = 'TestDatabase' + } + + It 'Should create a complete FileGroup with DataFile structure' { + # Create FileGroup + $fileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() + $fileGroup.Name = 'SecondaryFileGroup' + + # Create DataFile + $dataFile = [Microsoft.SqlServer.Management.Smo.DataFile]::new() + $dataFile.Name = 'SecondaryDataFile' + $dataFile.FileName = 'C:\Data\SecondaryDataFile.ndf' + + # Add DataFile to FileGroup + $fileGroup.Files.Add($dataFile) + + # Add FileGroup to Database + Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $fileGroup + + # Verify structure + $script:testDatabase.FileGroups[$fileGroup.Name] | Should -Be $fileGroup + $script:testDatabase.FileGroups[$fileGroup.Name].Files[$dataFile.Name] | Should -Be $dataFile + } + } +} diff --git a/tests/Unit/Public/Add-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/Add-SqlDscFileGroup.Tests.ps1 new file mode 100644 index 0000000000..59054f22e2 --- /dev/null +++ b/tests/Unit/Public/Add-SqlDscFileGroup.Tests.ps1 @@ -0,0 +1,149 @@ +<# + .SYNOPSIS + Unit tests for Add-SqlDscFileGroup. + + .DESCRIPTION + Unit tests for Add-SqlDscFileGroup. +#> + +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName + + # Loading SMO stub classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Add-SqlDscFileGroup' -Tag 'Public' { + Context 'When adding FileGroups to a Database' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' -ArgumentList @($mockServerObject, 'MyDatabase') + $mockFileGroupObject1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'FG1') + $mockFileGroupObject2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'FG2') + } + + It 'Should add a single FileGroup to the Database without returning output' { + { Add-SqlDscFileGroup -Database $mockDatabaseObject -FileGroup $mockFileGroupObject1 } | Should -Not -Throw + + $mockDatabaseObject.FileGroups.Count | Should -Be 1 + $mockDatabaseObject.FileGroups[0].Name | Should -Be 'FG1' + } + + It 'Should add a FileGroup and return it when PassThru is specified' { + $mockDatabaseObject2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' -ArgumentList @($mockServerObject, 'MyDatabase2') + $mockFileGroupObject3 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject2, 'FG3') + + $result = Add-SqlDscFileGroup -Database $mockDatabaseObject2 -FileGroup $mockFileGroupObject3 -PassThru + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'FG3' + $mockDatabaseObject2.FileGroups.Count | Should -Be 1 + } + + It 'Should add multiple FileGroups from an array' { + $mockDatabaseObject3 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' -ArgumentList @($mockServerObject, 'MyDatabase3') + $mockFileGroupObject4 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject3, 'FG4') + $mockFileGroupObject5 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject3, 'FG5') + $fileGroupArray = @($mockFileGroupObject4, $mockFileGroupObject5) + + { Add-SqlDscFileGroup -Database $mockDatabaseObject3 -FileGroup $fileGroupArray } | Should -Not -Throw + + $mockDatabaseObject3.FileGroups.Count | Should -Be 2 + $mockDatabaseObject3.FileGroups[0].Name | Should -Be 'FG4' + $mockDatabaseObject3.FileGroups[1].Name | Should -Be 'FG5' + } + + It 'Should accept FileGroups from pipeline' { + $mockDatabaseObject4 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' -ArgumentList @($mockServerObject, 'MyDatabase4') + $mockFileGroupObject6 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject4, 'FG6') + $mockFileGroupObject7 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject4, 'FG7') + + { @($mockFileGroupObject6, $mockFileGroupObject7) | Add-SqlDscFileGroup -Database $mockDatabaseObject4 } | Should -Not -Throw + + $mockDatabaseObject4.FileGroups.Count | Should -Be 2 + $mockDatabaseObject4.FileGroups[0].Name | Should -Be 'FG6' + $mockDatabaseObject4.FileGroups[1].Name | Should -Be 'FG7' + } + + It 'Should accept FileGroups from pipeline with PassThru' { + $mockDatabaseObject5 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' -ArgumentList @($mockServerObject, 'MyDatabase5') + $mockFileGroupObject8 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject5, 'FG8') + $mockFileGroupObject9 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject5, 'FG9') + + $result = @($mockFileGroupObject8, $mockFileGroupObject9) | Add-SqlDscFileGroup -Database $mockDatabaseObject5 -PassThru + + $result | Should -Not -BeNullOrEmpty + $result.Count | Should -Be 2 + $result[0].Name | Should -Be 'FG8' + $result[1].Name | Should -Be 'FG9' + $mockDatabaseObject5.FileGroups.Count | Should -Be 2 + } + } + + Context 'Parameter validation' { + It 'Should have Database as a mandatory parameter' { + (Get-Command -Name 'Add-SqlDscFileGroup').Parameters['Database'].Attributes.Mandatory | Should -BeTrue + } + + It 'Should have FileGroup as a mandatory parameter' { + (Get-Command -Name 'Add-SqlDscFileGroup').Parameters['FileGroup'].Attributes.Mandatory | Should -BeTrue + } + + It 'Should have PassThru as an optional parameter' { + (Get-Command -Name 'Add-SqlDscFileGroup').Parameters['PassThru'].Attributes.Mandatory | Should -BeFalse + } + + It 'Should have FileGroup parameter accept pipeline input' { + (Get-Command -Name 'Add-SqlDscFileGroup').Parameters['FileGroup'].Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have FileGroup parameter accept array input' { + (Get-Command -Name 'Add-SqlDscFileGroup').Parameters['FileGroup'].ParameterType.Name | Should -Be 'FileGroup[]' + } + } +} From 964ff6401ab01f1e1d3e3b81c1aab4a59d88754f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 15:59:53 +0100 Subject: [PATCH 08/87] Add New-SqlDscDataFile command and associated tests for creating DataFile objects in SQL Server --- source/Public/New-SqlDscDataFile.ps1 | 85 +++++++++ .../New-SqlDscDataFile.Integration.Tests.ps1 | 115 ++++++++++++ .../Unit/Public/New-SqlDscDataFile.Tests.ps1 | 169 ++++++++++++++++++ 3 files changed, 369 insertions(+) create mode 100644 source/Public/New-SqlDscDataFile.ps1 create mode 100644 tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 create mode 100644 tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 new file mode 100644 index 0000000000..50a1b70088 --- /dev/null +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -0,0 +1,85 @@ +<# + .SYNOPSIS + Creates a new DataFile object for a SQL Server FileGroup. + + .DESCRIPTION + This command creates a new DataFile object that can be added to a FileGroup + when creating or modifying SQL Server databases. The DataFile object represents + a physical database file (.mdf, .ndf, or .ss for snapshots). + + .PARAMETER FileGroup + Specifies the FileGroup object to which this DataFile will belong. If not + specified, the DataFile can be added to a FileGroup later. + + .PARAMETER Name + Specifies the logical name of the DataFile. + + .PARAMETER FileName + Specifies the physical path and filename for the DataFile. For database + snapshots, this should point to a sparse file location (typically with + .ss extension). For regular databases, this should be the data file + path (typically with .mdf or .ndf extension). + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $database = $serverObject.Databases['MyDatabase'] + $fileGroup = New-SqlDscFileGroup -Database $database -Name 'PRIMARY' + $dataFile = New-SqlDscDataFile -FileGroup $fileGroup -Name 'MyDatabase_Data' -FileName 'C:\Data\MyDatabase_Data.mdf' + + Creates a new DataFile for a regular database with a FileGroup. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $database = $serverObject.Databases['MyDatabase'] + $fileGroup = New-SqlDscFileGroup -Database $database -Name 'PRIMARY' + $dataFile = $fileGroup | New-SqlDscDataFile -Name 'MySnapshot_Data' -FileName 'C:\Snapshots\MySnapshot_Data.ss' + + Creates a new sparse file for a database snapshot using pipeline input. + + .EXAMPLE + $dataFile = New-SqlDscDataFile -Name 'MyDatabase_Data' -FileName 'C:\Data\MyDatabase_Data.mdf' + + Creates a standalone DataFile object without assigning it to a FileGroup. + + .OUTPUTS + `[Microsoft.SqlServer.Management.Smo.DataFile]` +#> +function New-SqlDscDataFile +{ + [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] + [CmdletBinding(DefaultParameterSetName = 'WithFileGroup')] + param + ( + [Parameter(ParameterSetName = 'WithFileGroup', ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.FileGroup] + $FileGroup, + + [Parameter(Mandatory = $true, ParameterSetName = 'WithFileGroup')] + [Parameter(Mandatory = $true, ParameterSetName = 'Standalone')] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter(Mandatory = $true, ParameterSetName = 'WithFileGroup')] + [Parameter(Mandatory = $true, ParameterSetName = 'Standalone')] + [ValidateNotNullOrEmpty()] + [System.String] + $FileName + ) + + process + { + if ($PSCmdlet.ParameterSetName -eq 'WithFileGroup') + { + $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroup, $Name, $FileName) + } + else + { + $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new() + $dataFileObject.Name = $Name + $dataFileObject.FileName = $FileName + } + + return $dataFileObject + } +} diff --git a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 new file mode 100644 index 0000000000..7732453286 --- /dev/null +++ b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 @@ -0,0 +1,115 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Import the SMO module to ensure real SMO types are available + Import-SqlDscPreferredModule +} + +Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + Context 'When creating a standalone DataFile with real SMO types' { + It 'Should create a standalone DataFile successfully' { + $result = New-SqlDscDataFile -Name 'TestDataFile' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'TestDataFile' + $result.Parent | Should -BeNullOrEmpty + } + + It 'Should create a standalone DataFile with FileName' { + $result = New-SqlDscDataFile -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'TestDataFile' + $result.FileName | Should -Be 'C:\Data\TestDataFile.ndf' + $result.Parent | Should -BeNullOrEmpty + } + } + + Context 'When creating a DataFile with a FileGroup object' { + BeforeAll { + # Create a real SMO FileGroup object (not connected to SQL Server) + $script:mockFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() + $script:mockFileGroup.Name = 'TestFileGroup' + } + + It 'Should create a DataFile with FileGroup successfully' { + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'TestDataFile' + $result.Parent | Should -Be $script:mockFileGroup + } + + It 'Should create a DataFile with FileGroup and FileName' { + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile2' -FileName 'C:\Data\TestDataFile2.ndf' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'TestDataFile2' + $result.FileName | Should -Be 'C:\Data\TestDataFile2.ndf' + $result.Parent | Should -Be $script:mockFileGroup + } + + It 'Should accept FileGroup parameter from pipeline' { + $result = $script:mockFileGroup | New-SqlDscDataFile -Name 'PipelineDataFile' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'PipelineDataFile' + $result.Parent | Should -Be $script:mockFileGroup + } + } + + Context 'When verifying DataFile properties' { + It 'Should allow setting Size property' { + $result = New-SqlDscDataFile -Name 'TestDataFile' + $result.Size = 1024.0 + + $result.Size | Should -Be 1024.0 + } + + It 'Should allow setting Growth property' { + $result = New-SqlDscDataFile -Name 'TestDataFile' + $result.Growth = 64.0 + + $result.Growth | Should -Be 64.0 + } + + It 'Should allow setting GrowthType property' { + $result = New-SqlDscDataFile -Name 'TestDataFile' + $result.GrowthType = [Microsoft.SqlServer.Management.Smo.FileGrowthType]::Percent + + $result.GrowthType | Should -Be 'Percent' + } + } +} diff --git a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 new file mode 100644 index 0000000000..a73cf61213 --- /dev/null +++ b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 @@ -0,0 +1,169 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'New-SqlDscDataFile' -Tag 'Public' { + Context 'When creating a new DataFile' { + BeforeAll { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject.Name = 'TestDatabase' + + $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'PRIMARY' + } + + It 'Should create a DataFile successfully' { + InModuleScope -Parameters @{ + mockFileGroupObject = $mockFileGroupObject + } -ScriptBlock { + param ($mockFileGroupObject) + + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MyDataFile' -FileName 'C:\Data\MyDataFile.mdf' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'MyDataFile' + $result.FileName | Should -Be 'C:\Data\MyDataFile.mdf' + $result.Parent | Should -Be $mockFileGroupObject + } + } + + It 'Should create a sparse file for database snapshot' { + InModuleScope -Parameters @{ + mockFileGroupObject = $mockFileGroupObject + } -ScriptBlock { + param ($mockFileGroupObject) + + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MySnapshot_Data' -FileName 'C:\Snapshots\MySnapshot_Data.ss' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'MySnapshot_Data' + $result.FileName | Should -Be 'C:\Snapshots\MySnapshot_Data.ss' + $result.Parent | Should -Be $mockFileGroupObject + } + } + + It 'Should accept FileGroup parameter from pipeline' { + InModuleScope -Parameters @{ + mockFileGroupObject = $mockFileGroupObject + } -ScriptBlock { + param ($mockFileGroupObject) + + $result = $mockFileGroupObject | New-SqlDscDataFile -Name 'PipelineDataFile' -FileName 'C:\Data\PipelineDataFile.ndf' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'PipelineDataFile' + $result.FileName | Should -Be 'C:\Data\PipelineDataFile.ndf' + $result.Parent | Should -Be $mockFileGroupObject + } + } + + Context 'When creating a standalone DataFile' { + It 'Should create a DataFile without FileGroup successfully' { + $result = New-SqlDscDataFile -Name 'StandaloneDataFile' -FileName 'C:\Data\StandaloneDataFile.mdf' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'StandaloneDataFile' + $result.FileName | Should -Be 'C:\Data\StandaloneDataFile.mdf' + $result.Parent | Should -BeNullOrEmpty + } + } + } + + Context 'Parameter validation' { + BeforeAll { + $commandInfo = Get-Command -Name 'New-SqlDscDataFile' + } + + It 'Should have two parameter sets' { + $commandInfo.ParameterSets.Count | Should -Be 2 + } + + It 'Should have parameter set WithFileGroup' { + $commandInfo.ParameterSets.Name | Should -Contain 'WithFileGroup' + } + + It 'Should have parameter set Standalone' { + $commandInfo.ParameterSets.Name | Should -Contain 'Standalone' + } + + It 'Should have WithFileGroup as the default parameter set' { + $defaultParameterSet = $commandInfo.ParameterSets | Where-Object { $_.IsDefault } + $defaultParameterSet.Name | Should -Be 'WithFileGroup' + } + + It 'Should have FileGroup as an optional parameter in WithFileGroup set' { + $parameterInfo = $commandInfo.Parameters['FileGroup'] + $withFileGroupAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [Parameter] -and $_.ParameterSetName -eq 'WithFileGroup' } + $withFileGroupAttribute.Mandatory | Should -BeFalse + } + + It 'Should not have FileGroup parameter in Standalone set' { + $parameterInfo = $commandInfo.Parameters['FileGroup'] + $standaloneAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [Parameter] -and $_.ParameterSetName -eq 'Standalone' } + $standaloneAttribute | Should -BeNullOrEmpty + } + + It 'Should have Name as a mandatory parameter in both parameter sets' { + $parameterInfo = $commandInfo.Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have FileName as a mandatory parameter in both parameter sets' { + $parameterInfo = $commandInfo.Parameters['FileName'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have FileGroup parameter accept pipeline input' { + $parameterInfo = $commandInfo.Parameters['FileGroup'] + $parameterInfo.Attributes.ValueFromPipeline | Should -Contain $true + } + } +} From 23482668b05e17c27d4a5d5d52149570dc94d4d3 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 16:00:05 +0100 Subject: [PATCH 09/87] Add New-SqlDscFileGroup command and associated tests for creating FileGroup objects in SQL Server --- source/Public/New-SqlDscFileGroup.ps1 | 75 ++++++++ .../New-SqlDscFileGroup.Integration.Tests.ps1 | 91 ++++++++++ .../Unit/Public/New-SqlDscFileGroup.Tests.ps1 | 160 ++++++++++++++++++ 3 files changed, 326 insertions(+) create mode 100644 source/Public/New-SqlDscFileGroup.ps1 create mode 100644 tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 create mode 100644 tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 new file mode 100644 index 0000000000..86722646b5 --- /dev/null +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -0,0 +1,75 @@ +<# + .SYNOPSIS + Creates a new FileGroup object for a SQL Server database. + + .DESCRIPTION + This command creates a new FileGroup object that can be used when creating + or modifying SQL Server databases. The FileGroup object can contain DataFile + objects. The FileGroup can be created with or without an associated Database, + allowing it to be added to a Database later using Add-SqlDscFileGroup. + + .PARAMETER Database + Specifies the Database object to which this FileGroup will belong. This parameter + is optional. If not specified, a standalone FileGroup is created that can be + added to a Database later. + + .PARAMETER Name + Specifies the name of the FileGroup to create. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $database = $serverObject.Databases['MyDatabase'] + $fileGroup = New-SqlDscFileGroup -Database $database -Name 'MyFileGroup' + + Creates a new FileGroup named 'MyFileGroup' for the specified database. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $database = $serverObject.Databases['MyDatabase'] + $fileGroup = $database | New-SqlDscFileGroup -Name 'PRIMARY' + + Creates a new PRIMARY FileGroup using pipeline input. + + .EXAMPLE + $fileGroup = New-SqlDscFileGroup -Name 'MyFileGroup' + # Later add to database + Add-SqlDscFileGroup -Database $database -FileGroup $fileGroup + + Creates a standalone FileGroup that can be added to a Database later. + + .OUTPUTS + `[Microsoft.SqlServer.Management.Smo.FileGroup]` +#> +function New-SqlDscFileGroup +{ + [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup])] + [CmdletBinding(DefaultParameterSetName = 'Standalone')] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'WithDatabase')] + [Microsoft.SqlServer.Management.Smo.Database] + $Database, + + [Parameter(Mandatory = $true, ParameterSetName = 'WithDatabase')] + [Parameter(Mandatory = $true, ParameterSetName = 'Standalone')] + [ValidateNotNullOrEmpty()] + [System.String] + $Name + ) + + process + { + if ($PSCmdlet.ParameterSetName -eq 'WithDatabase') + { + $fileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $Database, $Name + } + else + { + $fileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' + + $fileGroupObject.Name = $Name + } + + return $fileGroupObject + } +} diff --git a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 new file mode 100644 index 0000000000..8ebfa7f645 --- /dev/null +++ b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 @@ -0,0 +1,91 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Import the SMO module to ensure real SMO types are available + Import-SqlDscPreferredModule +} + +Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + Context 'When creating a standalone FileGroup with real SMO types' { + It 'Should create a standalone FileGroup successfully' { + $result = New-SqlDscFileGroup -Name 'TestFileGroup' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'TestFileGroup' + $result.Parent | Should -BeNullOrEmpty + } + + It 'Should create a standalone PRIMARY FileGroup successfully' { + $result = New-SqlDscFileGroup -Name 'PRIMARY' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'PRIMARY' + $result.Parent | Should -BeNullOrEmpty + } + } + + Context 'When creating a FileGroup with a Database object' { + BeforeAll { + # Create a real SMO Database object (not connected to SQL Server) + $script:mockDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() + $script:mockDatabase.Name = 'TestDatabase' + } + + It 'Should create a FileGroup with Database successfully' { + $result = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'TestFileGroup' + $result.Parent | Should -Be $script:mockDatabase + } + + It 'Should accept Database parameter from pipeline' { + $result = $script:mockDatabase | New-SqlDscFileGroup -Name 'PipelineFileGroup' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'PipelineFileGroup' + $result.Parent | Should -Be $script:mockDatabase + } + } + + Context 'When verifying FileGroup properties' { + It 'Should have Files collection initialized' { + $result = New-SqlDscFileGroup -Name 'TestFileGroup' + + $result.Files | Should -Not -BeNullOrEmpty + $result.Files | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFileCollection' + $result.Files.Count | Should -Be 0 + } + } +} diff --git a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 new file mode 100644 index 0000000000..68606d980b --- /dev/null +++ b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 @@ -0,0 +1,160 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'New-SqlDscFileGroup' -Tag 'Public' { + Context 'When creating a new FileGroup with a Database' { + BeforeAll { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject.Name = 'TestDatabase' + } + + It 'Should create a FileGroup successfully' { + InModuleScope -Parameters @{ + mockDatabaseObject = $mockDatabaseObject + } -ScriptBlock { + param ($mockDatabaseObject) + + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'MyFileGroup' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'MyFileGroup' + $result.Parent | Should -Be $mockDatabaseObject + } + } + + It 'Should create a PRIMARY FileGroup successfully' { + InModuleScope -Parameters @{ + mockDatabaseObject = $mockDatabaseObject + } -ScriptBlock { + param ($mockDatabaseObject) + + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'PRIMARY' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'PRIMARY' + $result.Parent | Should -Be $mockDatabaseObject + } + } + + It 'Should accept Database parameter from pipeline' { + InModuleScope -Parameters @{ + mockDatabaseObject = $mockDatabaseObject + } -ScriptBlock { + param ($mockDatabaseObject) + + $result = $mockDatabaseObject | New-SqlDscFileGroup -Name 'PipelineFileGroup' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'PipelineFileGroup' + $result.Parent | Should -Be $mockDatabaseObject + } + } + } + + Context 'When creating a standalone FileGroup' { + It 'Should create a standalone FileGroup without a Database' { + InModuleScope -ScriptBlock { + $result = New-SqlDscFileGroup -Name 'StandaloneFileGroup' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'StandaloneFileGroup' + $result.Parent | Should -BeNullOrEmpty + } + } + + It 'Should create a standalone PRIMARY FileGroup' { + InModuleScope -ScriptBlock { + $result = New-SqlDscFileGroup -Name 'PRIMARY' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'PRIMARY' + $result.Parent | Should -BeNullOrEmpty + } + } + } + + Context 'Parameter validation' { + It 'Should have Database as a mandatory parameter in WithDatabase parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] + $parameterSetInfo = $parameterInfo.ParameterSets['WithDatabase'] + $parameterSetInfo.IsMandatory | Should -BeTrue + } + + It 'Should have Database parameter not be in Standalone parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] + $parameterInfo.ParameterSets.Keys | Should -Not -Contain 'Standalone' + } + + It 'Should have Name as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have Database parameter accept pipeline input' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] + $parameterInfo.Attributes.ValueFromPipeline | Should -Contain $true + } + + It 'Should have two parameter sets' { + $command = Get-Command -Name 'New-SqlDscFileGroup' + $command.ParameterSets.Count | Should -Be 2 + $command.ParameterSets.Name | Should -Contain 'WithDatabase' + $command.ParameterSets.Name | Should -Contain 'Standalone' + } + + It 'Should have Standalone as the default parameter set' { + $command = Get-Command -Name 'New-SqlDscFileGroup' + $command.DefaultParameterSet | Should -Be 'Standalone' + } + } +} From ba30af5fd66b98328fca45c8b3ed3132a535ad2c Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 16:57:35 +0100 Subject: [PATCH 10/87] Enhance New-SqlDscDatabaseSnapshot and related commands to support FileGroup and DataFile parameters for improved snapshot management and file placement in SQL Server. --- CHANGELOG.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b85283507a..b9b381ee05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added public command `New-SqlDscDatabaseSnapshot` to create database snapshots in a SQL Server Database Engine instance using SMO. This command provides an automated and DSC-friendly approach to snapshot management by leveraging - `New-SqlDscDatabase` for the actual creation. + `New-SqlDscDatabase` for the actual creation. The command now supports `FileGroup` + and `DataFile` parameters to allow control over snapshot file placement and + structure ([issue #2341](https://github.com/dsccommunity/SqlServerDsc/issues/2341)). +- Added public command `New-SqlDscFileGroup` to create FileGroup objects for SQL + Server databases. This command simplifies creating FileGroup objects that can be + used with `New-SqlDscDatabase` and other database-related commands. The `Database` + parameter is optional, allowing FileGroup objects to be created standalone and + added to a Database later using `Add-SqlDscFileGroup`. +- Added public command `New-SqlDscDataFile` to create DataFile objects for SQL + Server FileGroups. This command simplifies creating DataFile objects with + specified physical file paths, supporting both regular database files (.mdf, .ndf) + and sparse files for database snapshots (.ss). The `FileGroup` parameter is + optional, allowing DataFile objects to be created standalone and assigned to a + FileGroup later. +- Added public command `Add-SqlDscDataFile` to add an existing DataFile object + to a FileGroup. This command provides a clean way to associate DataFile objects + with FileGroups after they have been created. +- Added public command `Add-SqlDscFileGroup` to add one or more FileGroup objects + to a Database. This command provides a clean way to associate FileGroup objects + with a Database after they have been created. +- `New-SqlDscDatabase` + - Added `FileGroup` and `DataFile` parameters to allow specifying custom file + locations and structure. These parameters apply to both regular databases and + database snapshots, enabling control over file placement for snapshots (sparse + files) and custom filegroup/datafile configuration for regular databases + ([issue #2341](https://github.com/dsccommunity/SqlServerDsc/issues/2341)). - Added public command `Set-SqlDscDatabaseOwner` to change the owner of a SQL Server database [issue #2177](https://github.com/dsccommunity/SqlServerDsc/issues/2177). This command uses the SMO `SetOwner()` method and supports both `ServerObject` From c0d43bafb2b892b1829034ef69144289d184025e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 17:05:31 +0100 Subject: [PATCH 11/87] Add Force parameter to New-SqlDscDataFile and New-SqlDscFileGroup for confirmation bypass; update tests to validate new functionality --- source/Public/New-SqlDscDataFile.ps1 | 31 ++++++++-- source/Public/New-SqlDscFileGroup.ps1 | 31 +++++++++- source/en-US/SqlServerDsc.strings.psd1 | 12 ++++ .../New-SqlDscDataFile.Integration.Tests.ps1 | 30 +++++++--- .../New-SqlDscFileGroup.Integration.Tests.ps1 | 20 ++++++- .../Unit/Public/New-SqlDscDataFile.Tests.ps1 | 59 ++++++++++++++++--- .../Unit/Public/New-SqlDscFileGroup.Tests.ps1 | 52 +++++++++++++++- 7 files changed, 209 insertions(+), 26 deletions(-) diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index 50a1b70088..e6853ec8cd 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -20,6 +20,11 @@ .ss extension). For regular databases, this should be the data file path (typically with .mdf or .ndf extension). + .PARAMETER Force + Specifies that the DataFile object should be created without prompting for + confirmation. By default, the command prompts for confirmation when the FileGroup + parameter is provided. + .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' $database = $serverObject.Databases['MyDatabase'] @@ -47,10 +52,10 @@ function New-SqlDscDataFile { [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] - [CmdletBinding(DefaultParameterSetName = 'WithFileGroup')] + [CmdletBinding(DefaultParameterSetName = 'Standalone', SupportsShouldProcess = $true, ConfirmImpact = 'High')] param ( - [Parameter(ParameterSetName = 'WithFileGroup', ValueFromPipeline = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'WithFileGroup', ValueFromPipeline = $true)] [Microsoft.SqlServer.Management.Smo.FileGroup] $FileGroup, @@ -64,14 +69,32 @@ function New-SqlDscDataFile [Parameter(Mandatory = $true, ParameterSetName = 'Standalone')] [ValidateNotNullOrEmpty()] [System.String] - $FileName + $FileName, + + [Parameter(ParameterSetName = 'WithFileGroup')] + [System.Management.Automation.SwitchParameter] + $Force ) process { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + $dataFileObject = $null + if ($PSCmdlet.ParameterSetName -eq 'WithFileGroup') { - $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroup, $Name, $FileName) + $descriptionMessage = $script:localizedData.DataFile_Create_ShouldProcessDescription -f $Name, $FileGroup.Name + $confirmationMessage = $script:localizedData.DataFile_Create_ShouldProcessConfirmation -f $Name + $captionMessage = $script:localizedData.DataFile_Create_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroup, $Name, $FileName) + } } else { diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index 86722646b5..3394aa4196 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -16,6 +16,11 @@ .PARAMETER Name Specifies the name of the FileGroup to create. + .PARAMETER Force + Specifies that the FileGroup object should be created without prompting for + confirmation. By default, the command prompts for confirmation when the Database + parameter is provided. + .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' $database = $serverObject.Databases['MyDatabase'] @@ -43,7 +48,7 @@ function New-SqlDscFileGroup { [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup])] - [CmdletBinding(DefaultParameterSetName = 'Standalone')] + [CmdletBinding(DefaultParameterSetName = 'Standalone', SupportsShouldProcess = $true, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'WithDatabase')] @@ -54,14 +59,34 @@ function New-SqlDscFileGroup [Parameter(Mandatory = $true, ParameterSetName = 'Standalone')] [ValidateNotNullOrEmpty()] [System.String] - $Name + $Name, + + [Parameter(ParameterSetName = 'WithDatabase')] + [System.Management.Automation.SwitchParameter] + $Force ) process { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + $fileGroupObject = $null + if ($PSCmdlet.ParameterSetName -eq 'WithDatabase') { - $fileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $Database, $Name + $serverObject = $Database.Parent + + $descriptionMessage = $script:localizedData.FileGroup_Create_ShouldProcessDescription -f $Name, $Database.Name, $serverObject.InstanceName + $confirmationMessage = $script:localizedData.FileGroup_Create_ShouldProcessConfirmation -f $Name + $captionMessage = $script:localizedData.FileGroup_Create_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + $fileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $Database, $Name + } } else { diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 4c552fa7e3..8f755e5954 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -369,6 +369,18 @@ ConvertFrom-StringData @' DatabaseSnapshot_Create = Creating database snapshot '{0}' from source database '{1}' on instance '{2}'. (NSDS0002) DatabaseSnapshot_EditionNotSupported = Database snapshots are not supported on SQL Server instance '{0}' with edition '{1}'. Snapshots are only supported in Enterprise, Developer, and Evaluation editions. (NSDS0001) + ## New-SqlDscFileGroup + FileGroup_Create_ShouldProcessDescription = Creating the filegroup '{0}' for database '{1}' on instance '{2}'. (NSDFG0001) + FileGroup_Create_ShouldProcessConfirmation = Are you sure you want to create the filegroup '{0}'? (NSDFG0002) + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + FileGroup_Create_ShouldProcessCaption = Create filegroup for database + + ## New-SqlDscDataFile + DataFile_Create_ShouldProcessDescription = Creating the data file '{0}' for filegroup '{1}'. (NSDDF0001) + DataFile_Create_ShouldProcessConfirmation = Are you sure you want to create the data file '{0}'? (NSDDF0002) + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + DataFile_Create_ShouldProcessCaption = Create data file for filegroup + ## Set-SqlDscDatabaseProperty Database_Set = Setting properties of database '{0}' on instance '{1}'. (SSDDP0001) Database_Updating = Updating database '{0}'. (SSDDP0002) diff --git a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 index 7732453286..5e4d27f86d 100644 --- a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 @@ -35,11 +35,12 @@ BeforeAll { Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { Context 'When creating a standalone DataFile with real SMO types' { It 'Should create a standalone DataFile successfully' { - $result = New-SqlDscDataFile -Name 'TestDataFile' + $result = New-SqlDscDataFile -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.mdf' $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' $result.Name | Should -Be 'TestDataFile' + $result.FileName | Should -Be 'C:\Data\TestDataFile.mdf' $result.Parent | Should -BeNullOrEmpty } @@ -62,7 +63,7 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 } It 'Should create a DataFile with FileGroup successfully' { - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' @@ -71,7 +72,7 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 } It 'Should create a DataFile with FileGroup and FileName' { - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile2' -FileName 'C:\Data\TestDataFile2.ndf' + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile2' -FileName 'C:\Data\TestDataFile2.ndf' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' @@ -81,32 +82,47 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 } It 'Should accept FileGroup parameter from pipeline' { - $result = $script:mockFileGroup | New-SqlDscDataFile -Name 'PipelineDataFile' + $result = $script:mockFileGroup | New-SqlDscDataFile -Name 'PipelineDataFile' -FileName 'C:\Data\PipelineDataFile.ndf' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' $result.Name | Should -Be 'PipelineDataFile' $result.Parent | Should -Be $script:mockFileGroup } + + It 'Should support Force parameter to bypass confirmation' { + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'ForcedDataFile' -FileName 'C:\Data\ForcedDataFile.ndf' -Force + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'ForcedDataFile' + $result.Parent | Should -Be $script:mockFileGroup + } + + It 'Should return null when user declines confirmation' { + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'DeclinedDataFile' -FileName 'C:\Data\DeclinedDataFile.ndf' -Confirm:$false -WhatIf + + $result | Should -BeNullOrEmpty + } } Context 'When verifying DataFile properties' { It 'Should allow setting Size property' { - $result = New-SqlDscDataFile -Name 'TestDataFile' + $result = New-SqlDscDataFile -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' $result.Size = 1024.0 $result.Size | Should -Be 1024.0 } It 'Should allow setting Growth property' { - $result = New-SqlDscDataFile -Name 'TestDataFile' + $result = New-SqlDscDataFile -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' $result.Growth = 64.0 $result.Growth | Should -Be 64.0 } It 'Should allow setting GrowthType property' { - $result = New-SqlDscDataFile -Name 'TestDataFile' + $result = New-SqlDscDataFile -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' $result.GrowthType = [Microsoft.SqlServer.Management.Smo.FileGrowthType]::Percent $result.GrowthType | Should -Be 'Percent' diff --git a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 index 8ebfa7f645..6c877d157c 100644 --- a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 @@ -61,7 +61,7 @@ Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 } It 'Should create a FileGroup with Database successfully' { - $result = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' + $result = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' @@ -70,13 +70,28 @@ Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 } It 'Should accept Database parameter from pipeline' { - $result = $script:mockDatabase | New-SqlDscFileGroup -Name 'PipelineFileGroup' + $result = $script:mockDatabase | New-SqlDscFileGroup -Name 'PipelineFileGroup' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' $result.Name | Should -Be 'PipelineFileGroup' $result.Parent | Should -Be $script:mockDatabase } + + It 'Should support Force parameter to bypass confirmation' { + $result = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'ForcedFileGroup' -Force + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'ForcedFileGroup' + $result.Parent | Should -Be $script:mockDatabase + } + + It 'Should return null when user declines confirmation' { + $result = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'DeclinedFileGroup' -Confirm:$false -WhatIf + + $result | Should -BeNullOrEmpty + } } Context 'When verifying FileGroup properties' { @@ -89,3 +104,4 @@ Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 } } } + diff --git a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 index a73cf61213..9be1df9844 100644 --- a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 @@ -64,7 +64,7 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { } -ScriptBlock { param ($mockFileGroupObject) - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MyDataFile' -FileName 'C:\Data\MyDataFile.mdf' + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MyDataFile' -FileName 'C:\Data\MyDataFile.mdf' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' @@ -80,7 +80,7 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { } -ScriptBlock { param ($mockFileGroupObject) - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MySnapshot_Data' -FileName 'C:\Snapshots\MySnapshot_Data.ss' + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MySnapshot_Data' -FileName 'C:\Snapshots\MySnapshot_Data.ss' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result.Name | Should -Be 'MySnapshot_Data' @@ -95,7 +95,7 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { } -ScriptBlock { param ($mockFileGroupObject) - $result = $mockFileGroupObject | New-SqlDscDataFile -Name 'PipelineDataFile' -FileName 'C:\Data\PipelineDataFile.ndf' + $result = $mockFileGroupObject | New-SqlDscDataFile -Name 'PipelineDataFile' -FileName 'C:\Data\PipelineDataFile.ndf' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result.Name | Should -Be 'PipelineDataFile' @@ -104,6 +104,33 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { } } + It 'Should support Force parameter to bypass confirmation' { + InModuleScope -Parameters @{ + mockFileGroupObject = $mockFileGroupObject + } -ScriptBlock { + param ($mockFileGroupObject) + + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'ForcedDataFile' -FileName 'C:\Data\ForcedDataFile.mdf' -Force + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'ForcedDataFile' + $result.FileName | Should -Be 'C:\Data\ForcedDataFile.mdf' + $result.Parent | Should -Be $mockFileGroupObject + } + } + + It 'Should return null when WhatIf is specified' { + InModuleScope -Parameters @{ + mockFileGroupObject = $mockFileGroupObject + } -ScriptBlock { + param ($mockFileGroupObject) + + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'WhatIfDataFile' -FileName 'C:\Data\WhatIfDataFile.mdf' -WhatIf + + $result | Should -BeNullOrEmpty + } + } + Context 'When creating a standalone DataFile' { It 'Should create a DataFile without FileGroup successfully' { $result = New-SqlDscDataFile -Name 'StandaloneDataFile' -FileName 'C:\Data\StandaloneDataFile.mdf' @@ -134,15 +161,15 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { $commandInfo.ParameterSets.Name | Should -Contain 'Standalone' } - It 'Should have WithFileGroup as the default parameter set' { + It 'Should have Standalone as the default parameter set' { $defaultParameterSet = $commandInfo.ParameterSets | Where-Object { $_.IsDefault } - $defaultParameterSet.Name | Should -Be 'WithFileGroup' + $defaultParameterSet.Name | Should -Be 'Standalone' } - It 'Should have FileGroup as an optional parameter in WithFileGroup set' { + It 'Should have FileGroup as a mandatory parameter in WithFileGroup set' { $parameterInfo = $commandInfo.Parameters['FileGroup'] $withFileGroupAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [Parameter] -and $_.ParameterSetName -eq 'WithFileGroup' } - $withFileGroupAttribute.Mandatory | Should -BeFalse + $withFileGroupAttribute.Mandatory | Should -BeTrue } It 'Should not have FileGroup parameter in Standalone set' { @@ -165,5 +192,23 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { $parameterInfo = $commandInfo.Parameters['FileGroup'] $parameterInfo.Attributes.ValueFromPipeline | Should -Contain $true } + + It 'Should support ShouldProcess' { + $commandInfo.Parameters.ContainsKey('WhatIf') | Should -BeTrue + $commandInfo.Parameters.ContainsKey('Confirm') | Should -BeTrue + } + + It 'Should have Force parameter only in WithFileGroup parameter set' { + $parameterInfo = $commandInfo.Parameters['Force'] + $parameterInfo | Should -Not -BeNullOrEmpty + $parameterInfo.ParameterSets.Keys | Should -Contain 'WithFileGroup' + $parameterInfo.ParameterSets.Keys | Should -Not -Contain 'Standalone' + } + + It 'Should have ConfirmImpact set to High' { + $commandInfo.ScriptBlock.Attributes | Where-Object { $_.TypeId.Name -eq 'CmdletBindingAttribute' } | + ForEach-Object { $_.ConfirmImpact } | Should -Be 'High' + } } } + diff --git a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 index 68606d980b..9dce2b2c5d 100644 --- a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 @@ -62,7 +62,7 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { } -ScriptBlock { param ($mockDatabaseObject) - $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'MyFileGroup' + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'MyFileGroup' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' @@ -77,7 +77,7 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { } -ScriptBlock { param ($mockDatabaseObject) - $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'PRIMARY' + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'PRIMARY' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result.Name | Should -Be 'PRIMARY' @@ -91,13 +91,39 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { } -ScriptBlock { param ($mockDatabaseObject) - $result = $mockDatabaseObject | New-SqlDscFileGroup -Name 'PipelineFileGroup' + $result = $mockDatabaseObject | New-SqlDscFileGroup -Name 'PipelineFileGroup' -Confirm:$false $result | Should -Not -BeNullOrEmpty $result.Name | Should -Be 'PipelineFileGroup' $result.Parent | Should -Be $mockDatabaseObject } } + + It 'Should support Force parameter to bypass confirmation' { + InModuleScope -Parameters @{ + mockDatabaseObject = $mockDatabaseObject + } -ScriptBlock { + param ($mockDatabaseObject) + + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'ForcedFileGroup' -Force + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'ForcedFileGroup' + $result.Parent | Should -Be $mockDatabaseObject + } + } + + It 'Should return null when WhatIf is specified' { + InModuleScope -Parameters @{ + mockDatabaseObject = $mockDatabaseObject + } -ScriptBlock { + param ($mockDatabaseObject) + + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'WhatIfFileGroup' -WhatIf + + $result | Should -BeNullOrEmpty + } + } } Context 'When creating a standalone FileGroup' { @@ -156,5 +182,25 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { $command = Get-Command -Name 'New-SqlDscFileGroup' $command.DefaultParameterSet | Should -Be 'Standalone' } + + It 'Should support ShouldProcess' { + $command = Get-Command -Name 'New-SqlDscFileGroup' + $command.Parameters.ContainsKey('WhatIf') | Should -BeTrue + $command.Parameters.ContainsKey('Confirm') | Should -BeTrue + } + + It 'Should have Force parameter only in WithDatabase parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Force'] + $parameterInfo | Should -Not -BeNullOrEmpty + $parameterInfo.ParameterSets.Keys | Should -Contain 'WithDatabase' + $parameterInfo.ParameterSets.Keys | Should -Not -Contain 'Standalone' + } + + It 'Should have ConfirmImpact set to High' { + $command = Get-Command -Name 'New-SqlDscFileGroup' + $command.ScriptBlock.Attributes | Where-Object { $_.TypeId.Name -eq 'CmdletBindingAttribute' } | + ForEach-Object { $_.ConfirmImpact } | Should -Be 'High' + } } } + From 4be93c805dd120fa9aef4510073a0fca6bf9fc1b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 18:41:01 +0100 Subject: [PATCH 12/87] Fix OutputType attribute placement in New-SqlDscDataFile and New-SqlDscFileGroup functions for consistency --- source/Public/New-SqlDscDataFile.ps1 | 10 +++++----- source/Public/New-SqlDscFileGroup.ps1 | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index e6853ec8cd..26dfa3697b 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -15,10 +15,10 @@ Specifies the logical name of the DataFile. .PARAMETER FileName - Specifies the physical path and filename for the DataFile. For database - snapshots, this should point to a sparse file location (typically with - .ss extension). For regular databases, this should be the data file - path (typically with .mdf or .ndf extension). + Specifies the physical path and filename for the DataFile. For database snapshots, + this should point to a sparse file location (typically with an .ss extension). + For regular databases, this should be the data file path (typically with .mdf + or .ndf extension). .PARAMETER Force Specifies that the DataFile object should be created without prompting for @@ -51,8 +51,8 @@ #> function New-SqlDscDataFile { - [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] [CmdletBinding(DefaultParameterSetName = 'Standalone', SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'WithFileGroup', ValueFromPipeline = $true)] diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index 3394aa4196..d00927bc0d 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -47,8 +47,8 @@ #> function New-SqlDscFileGroup { - [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup])] [CmdletBinding(DefaultParameterSetName = 'Standalone', SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'WithDatabase')] From ce382514bbe20a4b96d5981726cf9b1e4aef2045 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 18:41:07 +0100 Subject: [PATCH 13/87] Add tests for validating comment-based help structure and check for invalid help directives --- tests/QA/module.tests.ps1 | 71 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/tests/QA/module.tests.ps1 b/tests/QA/module.tests.ps1 index 463822a9cb..8b975b1930 100644 --- a/tests/QA/module.tests.ps1 +++ b/tests/QA/module.tests.ps1 @@ -179,6 +179,66 @@ Describe 'Quality for module' -Tags 'TestQuality' { } } +Describe 'Comment-based help structure' -Tags 'helpQuality' { + Context 'Validating comment-based help structure for ' -ForEach $testCasesAllModuleFunction -Tag 'helpQuality' { + BeforeAll { + $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" + + $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName + } + + It 'Should not have invalid help directives in comment-based help for ' { + # Valid help directives (case-insensitive) from: + # https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comment_based_help + $validDirectives = @( + 'SYNOPSIS' + 'DESCRIPTION' + 'PARAMETER' + 'EXAMPLE' + 'INPUTS' + 'OUTPUTS' + 'NOTES' + 'LINK' + 'COMPONENT' + 'ROLE' + 'FUNCTIONALITY' + 'FORWARDHELPTARGETNAME' + 'FORWARDHELPCATEGORY' + 'REMOTEHELPRUNSPACE' + 'EXTERNALHELP' + ) + + # Find the comment-based help block + if ($scriptFileRawContent -match '(?s)<#(.*?)#>') + { + $helpBlock = $Matches[1] + + # Split into lines to check each one + $helpLines = $helpBlock -split "`n" + + $invalidDirectives = @() + + foreach ($line in $helpLines) + { + # Check if line starts with whitespace followed by a period and text + if ($line -match '^\s+\.([a-zA-Z]+)') + { + $directive = $Matches[1] + + # Check if it's a valid directive + if ($directive -notin $validDirectives) + { + $invalidDirectives += $directive + } + } + } + + $invalidDirectives | Should -BeNullOrEmpty -Because ('invalid help directives found that will break help parsing: {0}' -f ($invalidDirectives -join ', ')) + } + } + } +} + Describe 'Help for module' -Tags 'helpQuality' { Context 'Validating help for ' -ForEach $testCasesAllModuleFunction -Tag 'helpQuality' { BeforeAll { @@ -186,7 +246,16 @@ Describe 'Help for module' -Tags 'helpQuality' { $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName - $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) + $parseErrors = $null + $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $parseErrors) + + if ($parseErrors) + { + foreach ($parseError in $parseErrors) + { + Write-Warning -Message ('Parse error in {0}: {1}' -f $functionFile.FullName, $parseError.Message) + } + } $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } From 0e6880899671071ef287b8a2a572501e54de7c2e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 19:07:46 +0100 Subject: [PATCH 14/87] Add suppression for ScriptAnalyzer warning in Add-SqlDscDataFile function --- source/Public/Add-SqlDscDataFile.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Public/Add-SqlDscDataFile.ps1 b/source/Public/Add-SqlDscDataFile.ps1 index 1413cb5bf4..6e4c7e1cd2 100644 --- a/source/Public/Add-SqlDscDataFile.ps1 +++ b/source/Public/Add-SqlDscDataFile.ps1 @@ -38,6 +38,7 @@ #> function Add-SqlDscDataFile { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [CmdletBinding()] [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] param From 7fa7591741718f917f573f1eef542c66b5b00bd7 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 1 Nov 2025 19:31:27 +0100 Subject: [PATCH 15/87] Add suppression for ScriptAnalyzer warning in Add-SqlDscFileGroup function --- source/Public/Add-SqlDscFileGroup.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Public/Add-SqlDscFileGroup.ps1 b/source/Public/Add-SqlDscFileGroup.ps1 index 0100cf503f..76b0d8e2e0 100644 --- a/source/Public/Add-SqlDscFileGroup.ps1 +++ b/source/Public/Add-SqlDscFileGroup.ps1 @@ -32,6 +32,7 @@ #> function Add-SqlDscFileGroup { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [CmdletBinding()] [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup[]])] param From 6d242ec8345f023c08262a9a1394209fa7ee7a44 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 11:05:20 +0100 Subject: [PATCH 16/87] Add validation for Database object in New-SqlDscFileGroup function; throw error if Parent is missing --- source/Public/New-SqlDscFileGroup.ps1 | 14 ++++++++++++++ source/en-US/SqlServerDsc.strings.psd1 | 1 + .../New-SqlDscFileGroup.Integration.Tests.ps1 | 1 - tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 | 15 ++++++++++++++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index d00927bc0d..d00aa74729 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -77,6 +77,20 @@ function New-SqlDscFileGroup if ($PSCmdlet.ParameterSetName -eq 'WithDatabase') { + if (-not $Database.Parent) + { + $errorMessage = $script:localizedData.FileGroup_DatabaseMissingServerObject + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $errorMessage, + 'NSDFG0003', + [System.Management.Automation.ErrorCategory]::InvalidArgument, + $Database + ) + ) + } + $serverObject = $Database.Parent $descriptionMessage = $script:localizedData.FileGroup_Create_ShouldProcessDescription -f $Name, $Database.Name, $serverObject.InstanceName diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 8f755e5954..9ed1d16ef8 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -374,6 +374,7 @@ ConvertFrom-StringData @' FileGroup_Create_ShouldProcessConfirmation = Are you sure you want to create the filegroup '{0}'? (NSDFG0002) # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. FileGroup_Create_ShouldProcessCaption = Create filegroup for database + FileGroup_DatabaseMissingServerObject = The Database object must have a Server object attached to the Parent property. (NSDFG0003) ## New-SqlDscDataFile DataFile_Create_ShouldProcessDescription = Creating the data file '{0}' for filegroup '{1}'. (NSDDF0001) diff --git a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 index 6c877d157c..42512c6c50 100644 --- a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 @@ -104,4 +104,3 @@ Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 } } } - diff --git a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 index 9dce2b2c5d..8f108f393a 100644 --- a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 @@ -52,8 +52,12 @@ AfterAll { Describe 'New-SqlDscFileGroup' -Tag 'Public' { Context 'When creating a new FileGroup with a Database' { BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' $mockDatabaseObject.Name = 'TestDatabase' + $mockDatabaseObject.Parent = $mockServerObject } It 'Should create a FileGroup successfully' { @@ -113,6 +117,16 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { } } + It 'Should throw terminating error when Database object has no Parent property set' { + InModuleScope -ScriptBlock { + $mockDatabaseWithoutParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseWithoutParent.Name = 'TestDatabaseNoParent' + + { New-SqlDscFileGroup -Database $mockDatabaseWithoutParent -Name 'InvalidFileGroup' -Confirm:$false } | + Should -Throw -ExpectedMessage '*must have a Server object attached to the Parent property*' -ErrorId 'NSDFG0003,New-SqlDscFileGroup' + } + } + It 'Should return null when WhatIf is specified' { InModuleScope -Parameters @{ mockDatabaseObject = $mockDatabaseObject @@ -203,4 +217,3 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { } } } - From 540de4768da28fd6186abcfd12a340c728a041db Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 11:05:32 +0100 Subject: [PATCH 17/87] Add mock setup for Database object in New-SqlDscFileGroup integration tests --- .../New-SqlDscFileGroup.Integration.Tests.ps1 | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 index 42512c6c50..c0e10edd9b 100644 --- a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 @@ -33,6 +33,18 @@ BeforeAll { } Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + } + Context 'When creating a standalone FileGroup with real SMO types' { It 'Should create a standalone FileGroup successfully' { $result = New-SqlDscFileGroup -Name 'TestFileGroup' @@ -55,9 +67,10 @@ Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 Context 'When creating a FileGroup with a Database object' { BeforeAll { - # Create a real SMO Database object (not connected to SQL Server) + # Create a real SMO Database object $script:mockDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() $script:mockDatabase.Name = 'TestDatabase' + $script:mockDatabase.Parent = $script:serverObject } It 'Should create a FileGroup with Database successfully' { From ea2b8b889169a4246f7dbd2843c742116fccfaec Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 11:05:45 +0100 Subject: [PATCH 18/87] Refactor New-SqlDscDataFile function to simplify parameter handling and update documentation for FileGroup parameter --- source/Public/New-SqlDscDataFile.ps1 | 41 +++++----------- .../New-SqlDscDataFile.Integration.Tests.ps1 | 48 ++++++++++--------- .../Unit/Public/New-SqlDscDataFile.Tests.ps1 | 48 +++---------------- 3 files changed, 44 insertions(+), 93 deletions(-) diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index 26dfa3697b..be6f139cce 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -8,8 +8,7 @@ a physical database file (.mdf, .ndf, or .ss for snapshots). .PARAMETER FileGroup - Specifies the FileGroup object to which this DataFile will belong. If not - specified, the DataFile can be added to a FileGroup later. + Specifies the FileGroup object to which this DataFile will belong. .PARAMETER Name Specifies the logical name of the DataFile. @@ -41,37 +40,30 @@ Creates a new sparse file for a database snapshot using pipeline input. - .EXAMPLE - $dataFile = New-SqlDscDataFile -Name 'MyDatabase_Data' -FileName 'C:\Data\MyDatabase_Data.mdf' - - Creates a standalone DataFile object without assigning it to a FileGroup. - .OUTPUTS `[Microsoft.SqlServer.Management.Smo.DataFile]` #> function New-SqlDscDataFile { - [CmdletBinding(DefaultParameterSetName = 'Standalone', SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] param ( - [Parameter(Mandatory = $true, ParameterSetName = 'WithFileGroup', ValueFromPipeline = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Microsoft.SqlServer.Management.Smo.FileGroup] $FileGroup, - [Parameter(Mandatory = $true, ParameterSetName = 'WithFileGroup')] - [Parameter(Mandatory = $true, ParameterSetName = 'Standalone')] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name, - [Parameter(Mandatory = $true, ParameterSetName = 'WithFileGroup')] - [Parameter(Mandatory = $true, ParameterSetName = 'Standalone')] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $FileName, - [Parameter(ParameterSetName = 'WithFileGroup')] + [Parameter()] [System.Management.Automation.SwitchParameter] $Force ) @@ -85,22 +77,13 @@ function New-SqlDscDataFile $dataFileObject = $null - if ($PSCmdlet.ParameterSetName -eq 'WithFileGroup') - { - $descriptionMessage = $script:localizedData.DataFile_Create_ShouldProcessDescription -f $Name, $FileGroup.Name - $confirmationMessage = $script:localizedData.DataFile_Create_ShouldProcessConfirmation -f $Name - $captionMessage = $script:localizedData.DataFile_Create_ShouldProcessCaption - - if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) - { - $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroup, $Name, $FileName) - } - } - else + $descriptionMessage = $script:localizedData.DataFile_Create_ShouldProcessDescription -f $Name, $FileGroup.Name + $confirmationMessage = $script:localizedData.DataFile_Create_ShouldProcessConfirmation -f $Name + $captionMessage = $script:localizedData.DataFile_Create_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) { - $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new() - $dataFileObject.Name = $Name - $dataFileObject.FileName = $FileName + $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroup, $Name, $FileName) } return $dataFileObject diff --git a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 index 5e4d27f86d..576df05e52 100644 --- a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 @@ -33,33 +33,26 @@ BeforeAll { } Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { - Context 'When creating a standalone DataFile with real SMO types' { - It 'Should create a standalone DataFile successfully' { - $result = New-SqlDscDataFile -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.mdf' + BeforeAll { + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $result.Name | Should -Be 'TestDataFile' - $result.FileName | Should -Be 'C:\Data\TestDataFile.mdf' - $result.Parent | Should -BeNullOrEmpty - } + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force - It 'Should create a standalone DataFile with FileName' { - $result = New-SqlDscDataFile -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $result.Name | Should -Be 'TestDataFile' - $result.FileName | Should -Be 'C:\Data\TestDataFile.ndf' - $result.Parent | Should -BeNullOrEmpty - } + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' } Context 'When creating a DataFile with a FileGroup object' { BeforeAll { - # Create a real SMO FileGroup object (not connected to SQL Server) - $script:mockFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() - $script:mockFileGroup.Name = 'TestFileGroup' + # Create a real SMO Database object + $script:mockDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() + $script:mockDatabase.Name = 'TestDatabase' + $script:mockDatabase.Parent = $script:serverObject + + $script:mockFileGroup = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' -Confirm:$false } It 'Should create a DataFile with FileGroup successfully' { @@ -107,22 +100,31 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 } Context 'When verifying DataFile properties' { + BeforeAll { + # Create a real SMO Database object + $script:mockDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() + $script:mockDatabase.Name = 'TestDatabase' + $script:mockDatabase.Parent = $script:serverObject + + $script:mockFileGroup = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' -Confirm:$false + } + It 'Should allow setting Size property' { - $result = New-SqlDscDataFile -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false $result.Size = 1024.0 $result.Size | Should -Be 1024.0 } It 'Should allow setting Growth property' { - $result = New-SqlDscDataFile -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false $result.Growth = 64.0 $result.Growth | Should -Be 64.0 } It 'Should allow setting GrowthType property' { - $result = New-SqlDscDataFile -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false $result.GrowthType = [Microsoft.SqlServer.Management.Smo.FileGrowthType]::Percent $result.GrowthType | Should -Be 'Percent' diff --git a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 index 9be1df9844..fa551b6882 100644 --- a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 @@ -130,18 +130,6 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { $result | Should -BeNullOrEmpty } } - - Context 'When creating a standalone DataFile' { - It 'Should create a DataFile without FileGroup successfully' { - $result = New-SqlDscDataFile -Name 'StandaloneDataFile' -FileName 'C:\Data\StandaloneDataFile.mdf' - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $result.Name | Should -Be 'StandaloneDataFile' - $result.FileName | Should -Be 'C:\Data\StandaloneDataFile.mdf' - $result.Parent | Should -BeNullOrEmpty - } - } } Context 'Parameter validation' { @@ -149,41 +137,21 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { $commandInfo = Get-Command -Name 'New-SqlDscDataFile' } - It 'Should have two parameter sets' { - $commandInfo.ParameterSets.Count | Should -Be 2 - } - - It 'Should have parameter set WithFileGroup' { - $commandInfo.ParameterSets.Name | Should -Contain 'WithFileGroup' + It 'Should have one parameter set' { + $commandInfo.ParameterSets.Count | Should -Be 1 } - It 'Should have parameter set Standalone' { - $commandInfo.ParameterSets.Name | Should -Contain 'Standalone' - } - - It 'Should have Standalone as the default parameter set' { - $defaultParameterSet = $commandInfo.ParameterSets | Where-Object { $_.IsDefault } - $defaultParameterSet.Name | Should -Be 'Standalone' - } - - It 'Should have FileGroup as a mandatory parameter in WithFileGroup set' { + It 'Should have FileGroup as a mandatory parameter' { $parameterInfo = $commandInfo.Parameters['FileGroup'] - $withFileGroupAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [Parameter] -and $_.ParameterSetName -eq 'WithFileGroup' } - $withFileGroupAttribute.Mandatory | Should -BeTrue - } - - It 'Should not have FileGroup parameter in Standalone set' { - $parameterInfo = $commandInfo.Parameters['FileGroup'] - $standaloneAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [Parameter] -and $_.ParameterSetName -eq 'Standalone' } - $standaloneAttribute | Should -BeNullOrEmpty + $parameterInfo.Attributes.Mandatory | Should -Contain $true } - It 'Should have Name as a mandatory parameter in both parameter sets' { + It 'Should have Name as a mandatory parameter' { $parameterInfo = $commandInfo.Parameters['Name'] $parameterInfo.Attributes.Mandatory | Should -Contain $true } - It 'Should have FileName as a mandatory parameter in both parameter sets' { + It 'Should have FileName as a mandatory parameter' { $parameterInfo = $commandInfo.Parameters['FileName'] $parameterInfo.Attributes.Mandatory | Should -Contain $true } @@ -198,11 +166,9 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { $commandInfo.Parameters.ContainsKey('Confirm') | Should -BeTrue } - It 'Should have Force parameter only in WithFileGroup parameter set' { + It 'Should have Force parameter' { $parameterInfo = $commandInfo.Parameters['Force'] $parameterInfo | Should -Not -BeNullOrEmpty - $parameterInfo.ParameterSets.Keys | Should -Contain 'WithFileGroup' - $parameterInfo.ParameterSets.Keys | Should -Not -Contain 'Standalone' } It 'Should have ConfirmImpact set to High' { From 8cdb4eee9c836f509c7b870e043f20de3edc54e4 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 11:17:00 +0100 Subject: [PATCH 19/87] Enhance integration tests for Add-SqlDscDataFile and Add-SqlDscFileGroup by using real SMO Database and FileGroup objects --- .../Add-SqlDscDataFile.Integration.Tests.ps1 | 44 ++++++++++++------- .../Add-SqlDscFileGroup.Integration.Tests.ps1 | 44 +++++++++++-------- 2 files changed, 52 insertions(+), 36 deletions(-) diff --git a/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 index 4a084222e1..38cd97a734 100644 --- a/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 @@ -30,16 +30,28 @@ BeforeAll { # Import the SMO module to ensure real SMO types are available Import-SqlDscPreferredModule + + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' } Describe 'Add-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { Context 'When adding a DataFile to a FileGroup with real SMO types' { BeforeEach { - # Create real SMO objects - $script:testFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() - $script:testFileGroup.Name = 'TestFileGroup' - $script:testDataFile = [Microsoft.SqlServer.Management.Smo.DataFile]::new() - $script:testDataFile.Name = 'TestDataFile' + # Create real SMO Database and FileGroup objects + $script:testDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() + $script:testDatabase.Name = 'TestDatabase' + $script:testDatabase.Parent = $script:serverObject + + $script:testFileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'TestFileGroup' -Confirm:$false + $script:testDataFile = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false } It 'Should add a DataFile to FileGroup successfully' { @@ -68,10 +80,8 @@ Describe 'Add-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 } It 'Should add multiple DataFiles to FileGroup' { - $dataFile1 = [Microsoft.SqlServer.Management.Smo.DataFile]::new() - $dataFile1.Name = 'DataFile1' - $dataFile2 = [Microsoft.SqlServer.Management.Smo.DataFile]::new() - $dataFile2.Name = 'DataFile2' + $dataFile1 = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'DataFile1' -FileName 'C:\Data\DataFile1.ndf' -Confirm:$false + $dataFile2 = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'DataFile2' -FileName 'C:\Data\DataFile2.ndf' -Confirm:$false $initialCount = $script:testFileGroup.Files.Count @@ -83,10 +93,8 @@ Describe 'Add-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 } It 'Should add multiple DataFiles via pipeline and return them with PassThru' { - $dataFile1 = [Microsoft.SqlServer.Management.Smo.DataFile]::new() - $dataFile1.Name = 'DataFile1' - $dataFile2 = [Microsoft.SqlServer.Management.Smo.DataFile]::new() - $dataFile2.Name = 'DataFile2' + $dataFile1 = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'DataFile1' -FileName 'C:\Data\DataFile1.ndf' -Confirm:$false + $dataFile2 = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'DataFile2' -FileName 'C:\Data\DataFile2.ndf' -Confirm:$false $result = @($dataFile1, $dataFile2) | Add-SqlDscDataFile -FileGroup $script:testFileGroup -PassThru @@ -98,10 +106,12 @@ Describe 'Add-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 Context 'When verifying DataFile parent relationship' { BeforeEach { - $script:testFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() - $script:testFileGroup.Name = 'TestFileGroup' - $script:testDataFile = [Microsoft.SqlServer.Management.Smo.DataFile]::new() - $script:testDataFile.Name = 'TestDataFile' + $script:testDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() + $script:testDatabase.Name = 'TestDatabase' + $script:testDatabase.Parent = $script:serverObject + + $script:testFileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'TestFileGroup' -Confirm:$false + $script:testDataFile = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false } It 'Should update DataFile parent reference when added to FileGroup' { diff --git a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 index 6a8f700379..4a7b163375 100644 --- a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 @@ -30,16 +30,27 @@ BeforeAll { # Import the SMO module to ensure real SMO types are available Import-SqlDscPreferredModule + + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' } Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { Context 'When adding a FileGroup to a Database with real SMO types' { BeforeEach { - # Create real SMO objects + # Create real SMO Database object $script:testDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() $script:testDatabase.Name = 'TestDatabase' - $script:testFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() - $script:testFileGroup.Name = 'TestFileGroup' + $script:testDatabase.Parent = $script:serverObject + + $script:testFileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'TestFileGroup' -Confirm:$false } It 'Should add a FileGroup to Database successfully' { @@ -68,10 +79,8 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 } It 'Should add multiple FileGroups to Database' { - $fileGroup1 = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() - $fileGroup1.Name = 'FileGroup1' - $fileGroup2 = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() - $fileGroup2.Name = 'FileGroup2' + $fileGroup1 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup1' -Confirm:$false + $fileGroup2 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup2' -Confirm:$false $initialCount = $script:testDatabase.FileGroups.Count @@ -83,10 +92,8 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 } It 'Should add multiple FileGroups via pipeline and return them with PassThru' { - $fileGroup1 = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() - $fileGroup1.Name = 'FileGroup1' - $fileGroup2 = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() - $fileGroup2.Name = 'FileGroup2' + $fileGroup1 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup1' -Confirm:$false + $fileGroup2 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup2' -Confirm:$false $result = @($fileGroup1, $fileGroup2) | Add-SqlDscFileGroup -Database $script:testDatabase -PassThru @@ -100,8 +107,9 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 BeforeEach { $script:testDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() $script:testDatabase.Name = 'TestDatabase' - $script:testFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() - $script:testFileGroup.Name = 'TestFileGroup' + $script:testDatabase.Parent = $script:serverObject + + $script:testFileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'TestFileGroup' -Confirm:$false } It 'Should update FileGroup parent reference when added to Database' { @@ -119,20 +127,18 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 BeforeEach { $script:testDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() $script:testDatabase.Name = 'TestDatabase' + $script:testDatabase.Parent = $script:serverObject } It 'Should create a complete FileGroup with DataFile structure' { # Create FileGroup - $fileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() - $fileGroup.Name = 'SecondaryFileGroup' + $fileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'SecondaryFileGroup' -Confirm:$false # Create DataFile - $dataFile = [Microsoft.SqlServer.Management.Smo.DataFile]::new() - $dataFile.Name = 'SecondaryDataFile' - $dataFile.FileName = 'C:\Data\SecondaryDataFile.ndf' + $dataFile = New-SqlDscDataFile -FileGroup $fileGroup -Name 'SecondaryDataFile' -FileName 'C:\Data\SecondaryDataFile.ndf' -Confirm:$false # Add DataFile to FileGroup - $fileGroup.Files.Add($dataFile) + Add-SqlDscDataFile -FileGroup $fileGroup -DataFile $dataFile # Add FileGroup to Database Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $fileGroup From 43d128fc714793400f198a892652647ad2c8794e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 12:19:17 +0100 Subject: [PATCH 20/87] Update CHANGELOG to reflect mandatory FileGroup parameter in New-SqlDscDataFile; add suppression for ScriptAnalyzer warning in function --- CHANGELOG.md | 3 +-- source/Public/New-SqlDscDataFile.ps1 | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b381ee05..db82a96fb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,8 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Server FileGroups. This command simplifies creating DataFile objects with specified physical file paths, supporting both regular database files (.mdf, .ndf) and sparse files for database snapshots (.ss). The `FileGroup` parameter is - optional, allowing DataFile objects to be created standalone and assigned to a - FileGroup later. + mandatory, requiring DataFile objects to be created with an associated FileGroup. - Added public command `Add-SqlDscDataFile` to add an existing DataFile object to a FileGroup. This command provides a clean way to associate DataFile objects with FileGroups after they have been created. diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index be6f139cce..0dc14e3bbc 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -45,6 +45,7 @@ #> function New-SqlDscDataFile { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] param From 2658deb0f3976a0bcddc2387a800e416eaeaec0b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 13:43:48 +0100 Subject: [PATCH 21/87] Update CHANGELOG and documentation for Add-SqlDscDataFile to support multiple DataFile objects; enhance tests for array input and PassThru functionality. --- CHANGELOG.md | 7 ++-- source/Public/Add-SqlDscDataFile.ps1 | 35 ++++++++++++------- .../Unit/Public/Add-SqlDscDataFile.Tests.ps1 | 30 ++++++++++++++++ 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db82a96fb0..3ee02f1d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,9 +39,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 specified physical file paths, supporting both regular database files (.mdf, .ndf) and sparse files for database snapshots (.ss). The `FileGroup` parameter is mandatory, requiring DataFile objects to be created with an associated FileGroup. -- Added public command `Add-SqlDscDataFile` to add an existing DataFile object - to a FileGroup. This command provides a clean way to associate DataFile objects - with FileGroups after they have been created. +- Added public command `Add-SqlDscDataFile` to add one or more existing DataFile + objects to a FileGroup. This command provides a clean way to associate DataFile + objects with FileGroups after they have been created. The command supports + accepting multiple DataFile objects as an array or through pipeline input. - Added public command `Add-SqlDscFileGroup` to add one or more FileGroup objects to a Database. This command provides a clean way to associate FileGroup objects with a Database after they have been created. diff --git a/source/Public/Add-SqlDscDataFile.ps1 b/source/Public/Add-SqlDscDataFile.ps1 index 6e4c7e1cd2..3e6651ec81 100644 --- a/source/Public/Add-SqlDscDataFile.ps1 +++ b/source/Public/Add-SqlDscDataFile.ps1 @@ -1,19 +1,20 @@ <# .SYNOPSIS - Adds a DataFile to a SQL Server FileGroup. + Adds one or more DataFile objects to a SQL Server FileGroup. .DESCRIPTION - This command adds an existing DataFile object to a FileGroup. The DataFile - must be created first using New-SqlDscDataFile or by other means. + This command adds one or more existing DataFile objects to a FileGroup. The DataFile + objects must be created first using New-SqlDscDataFile or by other means. .PARAMETER FileGroup Specifies the FileGroup object to which the DataFile will be added. .PARAMETER DataFile - Specifies the DataFile object to add to the FileGroup. + Specifies one or more DataFile objects to add to the FileGroup. This parameter + accepts pipeline input. .PARAMETER PassThru - Returns the DataFile object that was added to the FileGroup. + Returns the DataFile objects that were added to the FileGroup. .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' @@ -33,14 +34,21 @@ Creates a DataFile, adds it to the PRIMARY FileGroup, and returns the DataFile object using pipeline input. + .EXAMPLE + $dataFile1 = New-SqlDscDataFile -Name 'Data1' -FileName 'C:\Data\Data1.ndf' + $dataFile2 = New-SqlDscDataFile -Name 'Data2' -FileName 'C:\Data\Data2.ndf' + Add-SqlDscDataFile -FileGroup $fileGroup -DataFile @($dataFile1, $dataFile2) + + Adds multiple DataFiles to the FileGroup. + .OUTPUTS - None. Unless -PassThru is specified, in which case it returns `[Microsoft.SqlServer.Management.Smo.DataFile]`. + None. Unless -PassThru is specified, in which case it returns `[Microsoft.SqlServer.Management.Smo.DataFile[]]`. #> function Add-SqlDscDataFile { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [CmdletBinding()] - [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] + [OutputType([Microsoft.SqlServer.Management.Smo.DataFile[]])] param ( [Parameter(Mandatory = $true)] @@ -48,7 +56,7 @@ function Add-SqlDscDataFile $FileGroup, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [Microsoft.SqlServer.Management.Smo.DataFile] + [Microsoft.SqlServer.Management.Smo.DataFile[]] $DataFile, [Parameter()] @@ -58,11 +66,14 @@ function Add-SqlDscDataFile process { - $FileGroup.Files.Add($DataFile) - - if ($PassThru.IsPresent) + foreach ($dataFileObject in $DataFile) { - return $DataFile + $FileGroup.Files.Add($dataFileObject) + + if ($PassThru.IsPresent) + { + $dataFileObject + } } } } diff --git a/tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 index 8f12f3e5b7..268643ae4c 100644 --- a/tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 @@ -88,6 +88,31 @@ Describe 'Add-SqlDscDataFile' -Tag 'Public' { $mockFileGroupObject3.Files.Count | Should -Be 1 $mockFileGroupObject3.Files[0].Name | Should -Be 'MyDataFile3' } + + It 'Should add multiple DataFiles to FileGroup' { + $mockFileGroupObject4 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'QUATERNARY') + $mockDataFileObject4a = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject4, 'MyDataFile4a', 'C:\Data\MyDataFile4a.ndf') + $mockDataFileObject4b = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject4, 'MyDataFile4b', 'C:\Data\MyDataFile4b.ndf') + + { Add-SqlDscDataFile -FileGroup $mockFileGroupObject4 -DataFile @($mockDataFileObject4a, $mockDataFileObject4b) } | Should -Not -Throw + + $mockFileGroupObject4.Files.Count | Should -Be 2 + $mockFileGroupObject4.Files[0].Name | Should -Be 'MyDataFile4a' + $mockFileGroupObject4.Files[1].Name | Should -Be 'MyDataFile4b' + } + + It 'Should add multiple DataFiles via pipeline and return them with PassThru' { + $mockFileGroupObject5 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'QUINARY') + $mockDataFileObject5a = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject5, 'MyDataFile5a', 'C:\Data\MyDataFile5a.ndf') + $mockDataFileObject5b = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject5, 'MyDataFile5b', 'C:\Data\MyDataFile5b.ndf') + + $result = @($mockDataFileObject5a, $mockDataFileObject5b) | Add-SqlDscDataFile -FileGroup $mockFileGroupObject5 -PassThru + + $result | Should -HaveCount 2 + $result[0].Name | Should -Be 'MyDataFile5a' + $result[1].Name | Should -Be 'MyDataFile5b' + $mockFileGroupObject5.Files.Count | Should -Be 2 + } } Context 'Parameter validation' { @@ -114,5 +139,10 @@ Describe 'Add-SqlDscDataFile' -Tag 'Public' { $parameterInfo = $commandInfo.Parameters['DataFile'] $parameterInfo.Attributes.ValueFromPipeline | Should -Contain $true } + + It 'Should have DataFile parameter accept array input' { + $parameterInfo = $commandInfo.Parameters['DataFile'] + $parameterInfo.ParameterType.Name | Should -Be 'DataFile[]' + } } } From f14707aa18f4a076b90b3816d1541387c8dfd299 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 13:44:01 +0100 Subject: [PATCH 22/87] Refactor New-SqlDscFileGroup integration tests to create a real SMO Database object for verifying FileGroup properties; update test for Files collection initialization. --- .../Add-SqlDscFileGroup.Integration.Tests.ps1 | 2 +- .../New-SqlDscFileGroup.Integration.Tests.ps1 | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 index 4a7b163375..39303431ce 100644 --- a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 @@ -109,7 +109,7 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 $script:testDatabase.Name = 'TestDatabase' $script:testDatabase.Parent = $script:serverObject - $script:testFileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'TestFileGroup' -Confirm:$false + $script:testFileGroup = New-SqlDscFileGroup -Name 'TestFileGroup' } It 'Should update FileGroup parent reference when added to Database' { diff --git a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 index c0e10edd9b..c618236d03 100644 --- a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 @@ -108,8 +108,15 @@ Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 } Context 'When verifying FileGroup properties' { - It 'Should have Files collection initialized' { - $result = New-SqlDscFileGroup -Name 'TestFileGroup' + BeforeAll { + # Create a real SMO Database object + $script:propertyTestDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() + $script:propertyTestDatabase.Name = 'PropertyTestDatabase' + $script:propertyTestDatabase.Parent = $script:serverObject + } + + It 'Should have Files collection initialized when attached to a Database' { + $result = New-SqlDscFileGroup -Database $script:propertyTestDatabase -Name 'TestFileGroup' -Confirm:$false $result.Files | Should -Not -BeNullOrEmpty $result.Files | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFileCollection' From 0877d6021056fc4e4fc97ebfe2634daa7d20e9f8 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 15:06:26 +0100 Subject: [PATCH 23/87] Refactor integration tests for Add-SqlDscDataFile and Add-SqlDscFileGroup to use real SMO Database and FileGroup objects; add AfterAll cleanup for database connections. --- .../Add-SqlDscDataFile.Integration.Tests.ps1 | 44 ++++++------------- .../Add-SqlDscFileGroup.Integration.Tests.ps1 | 23 +++++----- .../New-SqlDscDataFile.Integration.Tests.ps1 | 6 ++- .../New-SqlDscFileGroup.Integration.Tests.ps1 | 21 ++------- 4 files changed, 35 insertions(+), 59 deletions(-) diff --git a/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 index 38cd97a734..5c5084700b 100644 --- a/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 @@ -27,22 +27,25 @@ BeforeAll { $script:moduleName = 'SqlServerDsc' Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} - # Import the SMO module to ensure real SMO types are available - Import-SqlDscPreferredModule +Describe 'Add-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName - $script:mockInstanceName = 'DSCSQLTEST' - $script:mockComputerName = Get-ComputerName + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force - $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. - $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) - $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + } - $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' -} + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } -Describe 'Add-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { Context 'When adding a DataFile to a FileGroup with real SMO types' { BeforeEach { # Create real SMO Database and FileGroup objects @@ -103,25 +106,4 @@ Describe 'Add-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 $result[1] | Should -Be $dataFile2 } } - - Context 'When verifying DataFile parent relationship' { - BeforeEach { - $script:testDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() - $script:testDatabase.Name = 'TestDatabase' - $script:testDatabase.Parent = $script:serverObject - - $script:testFileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'TestFileGroup' -Confirm:$false - $script:testDataFile = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false - } - - It 'Should update DataFile parent reference when added to FileGroup' { - $script:testDataFile.Parent | Should -BeNullOrEmpty - - Add-SqlDscDataFile -FileGroup $script:testFileGroup -DataFile $script:testDataFile - - # Note: The parent may or may not be updated depending on SMO implementation - # This test verifies the DataFile is in the collection - $script:testFileGroup.Files[$script:testDataFile.Name] | Should -Be $script:testDataFile - } - } } diff --git a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 index 39303431ce..62a430c822 100644 --- a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 @@ -27,22 +27,25 @@ BeforeAll { $script:moduleName = 'SqlServerDsc' Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} - # Import the SMO module to ensure real SMO types are available - Import-SqlDscPreferredModule +Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName - $script:mockInstanceName = 'DSCSQLTEST' - $script:mockComputerName = Get-ComputerName + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force - $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. - $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) - $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + } - $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' -} + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } -Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { Context 'When adding a FileGroup to a Database with real SMO types' { BeforeEach { # Create real SMO Database object diff --git a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 index 576df05e52..b00563d791 100644 --- a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 @@ -33,7 +33,7 @@ BeforeAll { } Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { - BeforeAll { + BeforeAll { $script:mockInstanceName = 'DSCSQLTEST' $script:mockComputerName = Get-ComputerName @@ -45,6 +45,10 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' } + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } + Context 'When creating a DataFile with a FileGroup object' { BeforeAll { # Create a real SMO Database object diff --git a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 index c618236d03..bf97617690 100644 --- a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 @@ -45,6 +45,10 @@ Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' } + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } + Context 'When creating a standalone FileGroup with real SMO types' { It 'Should create a standalone FileGroup successfully' { $result = New-SqlDscFileGroup -Name 'TestFileGroup' @@ -106,21 +110,4 @@ Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 $result | Should -BeNullOrEmpty } } - - Context 'When verifying FileGroup properties' { - BeforeAll { - # Create a real SMO Database object - $script:propertyTestDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() - $script:propertyTestDatabase.Name = 'PropertyTestDatabase' - $script:propertyTestDatabase.Parent = $script:serverObject - } - - It 'Should have Files collection initialized when attached to a Database' { - $result = New-SqlDscFileGroup -Database $script:propertyTestDatabase -Name 'TestFileGroup' -Confirm:$false - - $result.Files | Should -Not -BeNullOrEmpty - $result.Files | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFileCollection' - $result.Files.Count | Should -Be 0 - } - } } From ada3a7bcaf2077c28fe49661988e64976da971ed Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 15:32:57 +0100 Subject: [PATCH 24/87] Add FileGroup parameter to New-SqlDscDatabase for custom file configurations; enhance tests for database snapshots with FileGroup support --- source/Public/New-SqlDscDatabase.ps1 | 28 ++++++++++ .../Unit/Public/New-SqlDscDatabase.Tests.ps1 | 56 ++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/source/Public/New-SqlDscDatabase.ps1 b/source/Public/New-SqlDscDatabase.ps1 index e070968673..57a63bff0b 100644 --- a/source/Public/New-SqlDscDatabase.ps1 +++ b/source/Public/New-SqlDscDatabase.ps1 @@ -38,6 +38,13 @@ When this parameter is specified, a database snapshot will be created instead of a regular database. The snapshot name is specified in the Name parameter. + .PARAMETER FileGroup + Specifies an array of FileGroup objects to add to the database. + Each FileGroup can contain DataFile objects with FileName properties + specifying the file paths. For database snapshots, the FileName must + point to sparse file locations. For regular databases, this allows + custom file and filegroup configuration. + .PARAMETER Force Specifies that the database should be created without any confirmation. @@ -68,6 +75,17 @@ Creates a database snapshot named **MyDatabaseSnapshot** from the source database **MyDatabase** without prompting for confirmation. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sourceDb = $serverObject.Databases['MyDatabase'] + $fileGroup = New-Object Microsoft.SqlServer.Management.Smo.FileGroup -ArgumentList $sourceDb, 'PRIMARY' + $dataFile = New-Object Microsoft.SqlServer.Management.Smo.DataFile -ArgumentList $fileGroup, 'MyDatabase_Data', 'C:\Snapshots\MyDatabase_Data.ss' + $fileGroup.Files.Add($dataFile) + $serverObject | New-SqlDscDatabase -Name 'MyDatabaseSnapshot' -DatabaseSnapshotBaseName 'MyDatabase' -FileGroup @($fileGroup) -Force + + Creates a database snapshot named **MyDatabaseSnapshot** from the source database **MyDatabase** + with a specified sparse file location without prompting for confirmation. + .OUTPUTS `[Microsoft.SqlServer.Management.Smo.Database]` #> @@ -115,6 +133,10 @@ function New-SqlDscDatabase [System.String] $DatabaseSnapshotBaseName, + [Parameter()] + [Microsoft.SqlServer.Management.Smo.FileGroup[]] + $FileGroup, + [Parameter()] [System.Management.Automation.SwitchParameter] $Force, @@ -282,6 +304,12 @@ function New-SqlDscDatabase } } + # Add FileGroups if provided (applies to both regular databases and snapshots) + if ($PSBoundParameters.ContainsKey('FileGroup')) + { + Add-SqlDscFileGroup -Database $sqlDatabaseObjectToCreate -FileGroup $FileGroup + } + Write-Verbose -Message ($script:localizedData.Database_Creating -f $Name) $sqlDatabaseObjectToCreate.Create() diff --git a/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 b/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 index 77931c1125..aa0a645fb2 100644 --- a/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 @@ -157,7 +157,7 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { It 'Should have the correct parameters in parameter set Database' -ForEach @( @{ ExpectedParameterSetName = 'Database' - ExpectedParameters = '-ServerObject -Name [-Collation ] [-CatalogCollation ] [-CompatibilityLevel ] [-RecoveryModel ] [-OwnerName ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + ExpectedParameters = '-ServerObject -Name [-Collation ] [-CatalogCollation ] [-CompatibilityLevel ] [-RecoveryModel ] [-OwnerName ] [-FileGroup ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscDatabase').ParameterSets | @@ -174,7 +174,7 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { It 'Should have the correct parameters in parameter set Snapshot' -ForEach @( @{ ExpectedParameterSetName = 'Snapshot' - ExpectedParameters = '-ServerObject -Name -DatabaseSnapshotBaseName [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + ExpectedParameters = '-ServerObject -Name -DatabaseSnapshotBaseName [-FileGroup ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscDatabase').ParameterSets | @@ -254,4 +254,56 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { Should -Throw -ExpectedMessage '*does not exist*' } } + + Context 'When creating a database snapshot with FileGroup' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'VersionMajor' -Value 15 -Force + + # Mock source database + $mockSourceDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockSourceDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'SourceDatabase' -Force + + $mockServerObject | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'SourceDatabase' = $mockSourceDatabase + } | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation + } -PassThru -Force + } -Force + + Mock -CommandName 'New-Object' -ParameterFilter { $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Database' } -MockWith { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject.Name = $ArgumentList[1] + $mockDatabaseObject.DatabaseSnapshotBaseName = $null + + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + # Mock implementation + } -Force + return $mockDatabaseObject + } + + # Mock the helper commands used by New-SqlDscDatabase + Mock -CommandName 'Add-SqlDscFileGroup' + } + + It 'Should create a database snapshot with FileGroup successfully' { + InModuleScope -Parameters @{ + mockServerObject = $mockServerObject + } -ScriptBlock { + $mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $null, 'PRIMARY' + $mockDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList $mockFileGroup, 'TestSnapshot_Data', 'C:\Snapshots\TestSnapshot_Data.ss' + $mockFileGroup.Files.Add($mockDataFile) + + $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseSnapshotBaseName 'SourceDatabase' -FileGroup @($mockFileGroup) -Force + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestSnapshot' + $result.DatabaseSnapshotBaseName | Should -Be 'SourceDatabase' + } + + Should -Invoke -CommandName 'Add-SqlDscFileGroup' -Exactly -Times 1 -Scope It + } + } } From 64e77ad23d9b456bb90fd7670ccab3aea53b4e95 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 15:33:05 +0100 Subject: [PATCH 25/87] Remove Add-SqlDscDataFile function and its associated tests; deprecated functionality --- source/Public/Add-SqlDscDataFile.ps1 | 79 ---------- .../Add-SqlDscDataFile.Integration.Tests.ps1 | 109 ------------- .../Unit/Public/Add-SqlDscDataFile.Tests.ps1 | 148 ------------------ 3 files changed, 336 deletions(-) delete mode 100644 source/Public/Add-SqlDscDataFile.ps1 delete mode 100644 tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 delete mode 100644 tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 diff --git a/source/Public/Add-SqlDscDataFile.ps1 b/source/Public/Add-SqlDscDataFile.ps1 deleted file mode 100644 index 3e6651ec81..0000000000 --- a/source/Public/Add-SqlDscDataFile.ps1 +++ /dev/null @@ -1,79 +0,0 @@ -<# - .SYNOPSIS - Adds one or more DataFile objects to a SQL Server FileGroup. - - .DESCRIPTION - This command adds one or more existing DataFile objects to a FileGroup. The DataFile - objects must be created first using New-SqlDscDataFile or by other means. - - .PARAMETER FileGroup - Specifies the FileGroup object to which the DataFile will be added. - - .PARAMETER DataFile - Specifies one or more DataFile objects to add to the FileGroup. This parameter - accepts pipeline input. - - .PARAMETER PassThru - Returns the DataFile objects that were added to the FileGroup. - - .EXAMPLE - $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - $database = $serverObject.Databases['MyDatabase'] - $fileGroup = $database.FileGroups['PRIMARY'] - $dataFile = New-SqlDscDataFile -Name 'MyDatabase_Data' -FileName 'C:\Data\MyDatabase_Data.mdf' - Add-SqlDscDataFile -FileGroup $fileGroup -DataFile $dataFile - - Creates a DataFile and adds it to the PRIMARY FileGroup. - - .EXAMPLE - $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - $database = $serverObject.Databases['MyDatabase'] - $fileGroup = $database.FileGroups['PRIMARY'] - $dataFile = New-SqlDscDataFile -Name 'MyDatabase_Data' -FileName 'C:\Data\MyDatabase_Data.mdf' | - Add-SqlDscDataFile -FileGroup $fileGroup -PassThru - - Creates a DataFile, adds it to the PRIMARY FileGroup, and returns the DataFile object using pipeline input. - - .EXAMPLE - $dataFile1 = New-SqlDscDataFile -Name 'Data1' -FileName 'C:\Data\Data1.ndf' - $dataFile2 = New-SqlDscDataFile -Name 'Data2' -FileName 'C:\Data\Data2.ndf' - Add-SqlDscDataFile -FileGroup $fileGroup -DataFile @($dataFile1, $dataFile2) - - Adds multiple DataFiles to the FileGroup. - - .OUTPUTS - None. Unless -PassThru is specified, in which case it returns `[Microsoft.SqlServer.Management.Smo.DataFile[]]`. -#> -function Add-SqlDscDataFile -{ - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] - [CmdletBinding()] - [OutputType([Microsoft.SqlServer.Management.Smo.DataFile[]])] - param - ( - [Parameter(Mandatory = $true)] - [Microsoft.SqlServer.Management.Smo.FileGroup] - $FileGroup, - - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [Microsoft.SqlServer.Management.Smo.DataFile[]] - $DataFile, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $PassThru - ) - - process - { - foreach ($dataFileObject in $DataFile) - { - $FileGroup.Files.Add($dataFileObject) - - if ($PassThru.IsPresent) - { - $dataFileObject - } - } - } -} diff --git a/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 deleted file mode 100644 index 5c5084700b..0000000000 --- a/tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1 +++ /dev/null @@ -1,109 +0,0 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] -param () - -BeforeDiscovery { - try - { - if (-not (Get-Module -Name 'DscResource.Test')) - { - # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. - if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) - { - # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null - } - - # If the dependencies have not been resolved, this will throw an error. - Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' - } - } - catch [System.IO.FileNotFoundException] - { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' - } -} - -BeforeAll { - $script:moduleName = 'SqlServerDsc' - - Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' -} - -Describe 'Add-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { - BeforeAll { - $script:mockInstanceName = 'DSCSQLTEST' - $script:mockComputerName = Get-ComputerName - - $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. - $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force - - $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) - - $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' - } - - AfterAll { - Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject - } - - Context 'When adding a DataFile to a FileGroup with real SMO types' { - BeforeEach { - # Create real SMO Database and FileGroup objects - $script:testDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() - $script:testDatabase.Name = 'TestDatabase' - $script:testDatabase.Parent = $script:serverObject - - $script:testFileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'TestFileGroup' -Confirm:$false - $script:testDataFile = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false - } - - It 'Should add a DataFile to FileGroup successfully' { - $initialCount = $script:testFileGroup.Files.Count - - Add-SqlDscDataFile -FileGroup $script:testFileGroup -DataFile $script:testDataFile - - $script:testFileGroup.Files.Count | Should -Be ($initialCount + 1) - $script:testFileGroup.Files[$script:testDataFile.Name] | Should -Be $script:testDataFile - } - - It 'Should return DataFile when using PassThru' { - $result = Add-SqlDscDataFile -FileGroup $script:testFileGroup -DataFile $script:testDataFile -PassThru - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $result | Should -Be $script:testDataFile - } - - It 'Should accept DataFile from pipeline' { - $initialCount = $script:testFileGroup.Files.Count - - $script:testDataFile | Add-SqlDscDataFile -FileGroup $script:testFileGroup - - $script:testFileGroup.Files.Count | Should -Be ($initialCount + 1) - } - - It 'Should add multiple DataFiles to FileGroup' { - $dataFile1 = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'DataFile1' -FileName 'C:\Data\DataFile1.ndf' -Confirm:$false - $dataFile2 = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'DataFile2' -FileName 'C:\Data\DataFile2.ndf' -Confirm:$false - - $initialCount = $script:testFileGroup.Files.Count - - Add-SqlDscDataFile -FileGroup $script:testFileGroup -DataFile @($dataFile1, $dataFile2) - - $script:testFileGroup.Files.Count | Should -Be ($initialCount + 2) - $script:testFileGroup.Files[$dataFile1.Name] | Should -Be $dataFile1 - $script:testFileGroup.Files[$dataFile2.Name] | Should -Be $dataFile2 - } - - It 'Should add multiple DataFiles via pipeline and return them with PassThru' { - $dataFile1 = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'DataFile1' -FileName 'C:\Data\DataFile1.ndf' -Confirm:$false - $dataFile2 = New-SqlDscDataFile -FileGroup $script:testFileGroup -Name 'DataFile2' -FileName 'C:\Data\DataFile2.ndf' -Confirm:$false - - $result = @($dataFile1, $dataFile2) | Add-SqlDscDataFile -FileGroup $script:testFileGroup -PassThru - - $result | Should -HaveCount 2 - $result[0] | Should -Be $dataFile1 - $result[1] | Should -Be $dataFile2 - } - } -} diff --git a/tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 deleted file mode 100644 index 268643ae4c..0000000000 --- a/tests/Unit/Public/Add-SqlDscDataFile.Tests.ps1 +++ /dev/null @@ -1,148 +0,0 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] -param () - -BeforeDiscovery { - try - { - if (-not (Get-Module -Name 'DscResource.Test')) - { - # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. - if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) - { - # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null - } - - # If the dependencies have not been resolved, this will throw an error. - Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' - } - } - catch [System.IO.FileNotFoundException] - { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' - } -} - -BeforeAll { - $script:dscModuleName = 'SqlServerDsc' - - $env:SqlServerDscCI = $true - - Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' - - # Loading mocked classes - Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') - - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName -} - -AfterAll { - $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') - $PSDefaultParameterValues.Remove('Mock:ModuleName') - $PSDefaultParameterValues.Remove('Should:ModuleName') - - # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscModuleName -All | Remove-Module -Force - - Remove-Item -Path 'env:SqlServerDscCI' -} - -Describe 'Add-SqlDscDataFile' -Tag 'Public' { - Context 'When adding a DataFile to a FileGroup' { - BeforeAll { - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' - $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' -ArgumentList @($mockServerObject, 'MyDatabase') - $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'PRIMARY') - $mockDataFileObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject, 'MyDataFile', 'C:\Data\MyDataFile.mdf') - } - - It 'Should add the DataFile to the FileGroup without returning output' { - { Add-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFile $mockDataFileObject } | Should -Not -Throw - - $mockFileGroupObject.Files.Count | Should -Be 1 - $mockFileGroupObject.Files[0].Name | Should -Be 'MyDataFile' - $mockFileGroupObject.Files[0].FileName | Should -Be 'C:\Data\MyDataFile.mdf' - } - - It 'Should add the DataFile to the FileGroup and return the DataFile when PassThru is specified' { - $mockFileGroupObject2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'SECONDARY') - $mockDataFileObject2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject2, 'MyDataFile2', 'C:\Data\MyDataFile2.ndf') - - $result = Add-SqlDscDataFile -FileGroup $mockFileGroupObject2 -DataFile $mockDataFileObject2 -PassThru - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $result.Name | Should -Be 'MyDataFile2' - $result.FileName | Should -Be 'C:\Data\MyDataFile2.ndf' - $mockFileGroupObject2.Files.Count | Should -Be 1 - } - - It 'Should accept DataFile from pipeline' { - $mockFileGroupObject3 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'TERTIARY') - $mockDataFileObject3 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject3, 'MyDataFile3', 'C:\Data\MyDataFile3.ndf') - - { $mockDataFileObject3 | Add-SqlDscDataFile -FileGroup $mockFileGroupObject3 } | Should -Not -Throw - - $mockFileGroupObject3.Files.Count | Should -Be 1 - $mockFileGroupObject3.Files[0].Name | Should -Be 'MyDataFile3' - } - - It 'Should add multiple DataFiles to FileGroup' { - $mockFileGroupObject4 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'QUATERNARY') - $mockDataFileObject4a = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject4, 'MyDataFile4a', 'C:\Data\MyDataFile4a.ndf') - $mockDataFileObject4b = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject4, 'MyDataFile4b', 'C:\Data\MyDataFile4b.ndf') - - { Add-SqlDscDataFile -FileGroup $mockFileGroupObject4 -DataFile @($mockDataFileObject4a, $mockDataFileObject4b) } | Should -Not -Throw - - $mockFileGroupObject4.Files.Count | Should -Be 2 - $mockFileGroupObject4.Files[0].Name | Should -Be 'MyDataFile4a' - $mockFileGroupObject4.Files[1].Name | Should -Be 'MyDataFile4b' - } - - It 'Should add multiple DataFiles via pipeline and return them with PassThru' { - $mockFileGroupObject5 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList @($mockDatabaseObject, 'QUINARY') - $mockDataFileObject5a = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject5, 'MyDataFile5a', 'C:\Data\MyDataFile5a.ndf') - $mockDataFileObject5b = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList @($mockFileGroupObject5, 'MyDataFile5b', 'C:\Data\MyDataFile5b.ndf') - - $result = @($mockDataFileObject5a, $mockDataFileObject5b) | Add-SqlDscDataFile -FileGroup $mockFileGroupObject5 -PassThru - - $result | Should -HaveCount 2 - $result[0].Name | Should -Be 'MyDataFile5a' - $result[1].Name | Should -Be 'MyDataFile5b' - $mockFileGroupObject5.Files.Count | Should -Be 2 - } - } - - Context 'Parameter validation' { - BeforeAll { - $commandInfo = Get-Command -Name 'Add-SqlDscDataFile' - } - - It 'Should have FileGroup as a mandatory parameter' { - $parameterInfo = $commandInfo.Parameters['FileGroup'] - $parameterInfo.Attributes.Mandatory | Should -Contain $true - } - - It 'Should have DataFile as a mandatory parameter' { - $parameterInfo = $commandInfo.Parameters['DataFile'] - $parameterInfo.Attributes.Mandatory | Should -Contain $true - } - - It 'Should have PassThru as an optional parameter' { - $parameterInfo = $commandInfo.Parameters['PassThru'] - $parameterInfo.Attributes.Mandatory | Should -Not -Contain $true - } - - It 'Should have DataFile parameter accept pipeline input' { - $parameterInfo = $commandInfo.Parameters['DataFile'] - $parameterInfo.Attributes.ValueFromPipeline | Should -Contain $true - } - - It 'Should have DataFile parameter accept array input' { - $parameterInfo = $commandInfo.Parameters['DataFile'] - $parameterInfo.ParameterType.Name | Should -Be 'DataFile[]' - } - } -} From a904aaf5788be774ce504077437cced373a95737 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 15:39:15 +0100 Subject: [PATCH 26/87] Enhance New-SqlDscDataFile to automatically add DataFile to FileGroup; update documentation and tests for PassThru functionality --- source/Public/New-SqlDscDataFile.ps1 | 49 ++++++++++----- .../Unit/Public/New-SqlDscDataFile.Tests.ps1 | 63 +++++++++++++------ 2 files changed, 77 insertions(+), 35 deletions(-) diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index 0dc14e3bbc..f4cd4b63cd 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -1,14 +1,15 @@ <# .SYNOPSIS - Creates a new DataFile object for a SQL Server FileGroup. + Creates a new DataFile object for a SQL Server FileGroup and adds it to the FileGroup. .DESCRIPTION - This command creates a new DataFile object that can be added to a FileGroup - when creating or modifying SQL Server databases. The DataFile object represents - a physical database file (.mdf, .ndf, or .ss for snapshots). + This command creates a new DataFile object and automatically adds it to the specified + FileGroup's Files collection. The DataFile object represents a physical database file + (.mdf, .ndf, or .ss for snapshots). .PARAMETER FileGroup - Specifies the FileGroup object to which this DataFile will belong. + Specifies the FileGroup object to which this DataFile will belong. The DataFile + will be automatically added to this FileGroup's Files collection. .PARAMETER Name Specifies the logical name of the DataFile. @@ -19,6 +20,9 @@ For regular databases, this should be the data file path (typically with .mdf or .ndf extension). + .PARAMETER PassThru + Returns the DataFile object that was created and added to the FileGroup. + .PARAMETER Force Specifies that the DataFile object should be created without prompting for confirmation. By default, the command prompts for confirmation when the FileGroup @@ -28,20 +32,28 @@ $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' $database = $serverObject.Databases['MyDatabase'] $fileGroup = New-SqlDscFileGroup -Database $database -Name 'PRIMARY' - $dataFile = New-SqlDscDataFile -FileGroup $fileGroup -Name 'MyDatabase_Data' -FileName 'C:\Data\MyDatabase_Data.mdf' + New-SqlDscDataFile -FileGroup $fileGroup -Name 'MyDatabase_Data' -FileName 'C:\Data\MyDatabase_Data.mdf' -Force - Creates a new DataFile for a regular database with a FileGroup. + Creates a new DataFile for a regular database with a FileGroup and adds it to the FileGroup. .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' $database = $serverObject.Databases['MyDatabase'] $fileGroup = New-SqlDscFileGroup -Database $database -Name 'PRIMARY' - $dataFile = $fileGroup | New-SqlDscDataFile -Name 'MySnapshot_Data' -FileName 'C:\Snapshots\MySnapshot_Data.ss' + $dataFile = New-SqlDscDataFile -FileGroup $fileGroup -Name 'MySnapshot_Data' -FileName 'C:\Snapshots\MySnapshot_Data.ss' -PassThru -Force - Creates a new sparse file for a database snapshot using pipeline input. + Creates a new sparse file for a database snapshot and returns the DataFile object. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $database = $serverObject.Databases['MyDatabase'] + $fileGroup = $database.FileGroups['PRIMARY'] + $dataFile = New-SqlDscDataFile -FileGroup $fileGroup -Name 'AdditionalData' -FileName 'C:\Data\AdditionalData.ndf' -PassThru -Force + + Creates an additional DataFile and returns it for further processing. .OUTPUTS - `[Microsoft.SqlServer.Management.Smo.DataFile]` + None. Unless -PassThru is specified, in which case it returns `[Microsoft.SqlServer.Management.Smo.DataFile]`. #> function New-SqlDscDataFile { @@ -50,7 +62,7 @@ function New-SqlDscDataFile [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(Mandatory = $true)] [Microsoft.SqlServer.Management.Smo.FileGroup] $FileGroup, @@ -64,6 +76,10 @@ function New-SqlDscDataFile [System.String] $FileName, + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + [Parameter()] [System.Management.Automation.SwitchParameter] $Force @@ -76,8 +92,6 @@ function New-SqlDscDataFile $ConfirmPreference = 'None' } - $dataFileObject = $null - $descriptionMessage = $script:localizedData.DataFile_Create_ShouldProcessDescription -f $Name, $FileGroup.Name $confirmationMessage = $script:localizedData.DataFile_Create_ShouldProcessConfirmation -f $Name $captionMessage = $script:localizedData.DataFile_Create_ShouldProcessCaption @@ -85,8 +99,13 @@ function New-SqlDscDataFile if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) { $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroup, $Name, $FileName) - } - return $dataFileObject + $FileGroup.Files.Add($dataFileObject) + + if ($PassThru.IsPresent) + { + return $dataFileObject + } + } } } diff --git a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 index fa551b6882..6524e559fa 100644 --- a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 @@ -58,49 +58,66 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'PRIMARY' } - It 'Should create a DataFile successfully' { + It 'Should create a DataFile and add it to the FileGroup' { InModuleScope -Parameters @{ mockFileGroupObject = $mockFileGroupObject } -ScriptBlock { param ($mockFileGroupObject) - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MyDataFile' -FileName 'C:\Data\MyDataFile.mdf' -Confirm:$false + $initialFileCount = $mockFileGroupObject.Files.Count + + New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MyDataFile' -FileName 'C:\Data\MyDataFile.mdf' -Confirm:$false + + $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) + $addedFile = $mockFileGroupObject.Files | Where-Object -FilterScript { $_.Name -eq 'MyDataFile' } + $addedFile | Should -Not -BeNullOrEmpty + $addedFile.FileName | Should -Be 'C:\Data\MyDataFile.mdf' + } + } + + It 'Should return the created DataFile when PassThru is specified' { + InModuleScope -Parameters @{ + mockFileGroupObject = $mockFileGroupObject + } -ScriptBlock { + param ($mockFileGroupObject) + + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'PassThruDataFile' -FileName 'C:\Data\PassThruDataFile.mdf' -PassThru -Confirm:$false $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $result.Name | Should -Be 'MyDataFile' - $result.FileName | Should -Be 'C:\Data\MyDataFile.mdf' + $result.Name | Should -Be 'PassThruDataFile' + $result.FileName | Should -Be 'C:\Data\PassThruDataFile.mdf' $result.Parent | Should -Be $mockFileGroupObject } } - It 'Should create a sparse file for database snapshot' { + It 'Should not return anything when PassThru is not specified' { InModuleScope -Parameters @{ mockFileGroupObject = $mockFileGroupObject } -ScriptBlock { param ($mockFileGroupObject) - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MySnapshot_Data' -FileName 'C:\Snapshots\MySnapshot_Data.ss' -Confirm:$false + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'NoPassThruDataFile' -FileName 'C:\Data\NoPassThruDataFile.mdf' -Confirm:$false - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'MySnapshot_Data' - $result.FileName | Should -Be 'C:\Snapshots\MySnapshot_Data.ss' - $result.Parent | Should -Be $mockFileGroupObject + $result | Should -BeNullOrEmpty } } - It 'Should accept FileGroup parameter from pipeline' { + It 'Should create a sparse file for database snapshot' { InModuleScope -Parameters @{ mockFileGroupObject = $mockFileGroupObject } -ScriptBlock { param ($mockFileGroupObject) - $result = $mockFileGroupObject | New-SqlDscDataFile -Name 'PipelineDataFile' -FileName 'C:\Data\PipelineDataFile.ndf' -Confirm:$false + $initialFileCount = $mockFileGroupObject.Files.Count + + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MySnapshot_Data' -FileName 'C:\Snapshots\MySnapshot_Data.ss' -PassThru -Confirm:$false $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'PipelineDataFile' - $result.FileName | Should -Be 'C:\Data\PipelineDataFile.ndf' + $result.Name | Should -Be 'MySnapshot_Data' + $result.FileName | Should -Be 'C:\Snapshots\MySnapshot_Data.ss' $result.Parent | Should -Be $mockFileGroupObject + $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) } } @@ -110,24 +127,30 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { } -ScriptBlock { param ($mockFileGroupObject) - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'ForcedDataFile' -FileName 'C:\Data\ForcedDataFile.mdf' -Force + $initialFileCount = $mockFileGroupObject.Files.Count + + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'ForcedDataFile' -FileName 'C:\Data\ForcedDataFile.mdf' -PassThru -Force $result | Should -Not -BeNullOrEmpty $result.Name | Should -Be 'ForcedDataFile' $result.FileName | Should -Be 'C:\Data\ForcedDataFile.mdf' $result.Parent | Should -Be $mockFileGroupObject + $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) } } - It 'Should return null when WhatIf is specified' { + It 'Should not add file when WhatIf is specified' { InModuleScope -Parameters @{ mockFileGroupObject = $mockFileGroupObject } -ScriptBlock { param ($mockFileGroupObject) + $initialFileCount = $mockFileGroupObject.Files.Count + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'WhatIfDataFile' -FileName 'C:\Data\WhatIfDataFile.mdf' -WhatIf $result | Should -BeNullOrEmpty + $mockFileGroupObject.Files.Count | Should -Be $initialFileCount } } } @@ -156,9 +179,10 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { $parameterInfo.Attributes.Mandatory | Should -Contain $true } - It 'Should have FileGroup parameter accept pipeline input' { - $parameterInfo = $commandInfo.Parameters['FileGroup'] - $parameterInfo.Attributes.ValueFromPipeline | Should -Contain $true + It 'Should have PassThru parameter' { + $parameterInfo = $commandInfo.Parameters['PassThru'] + $parameterInfo | Should -Not -BeNullOrEmpty + $parameterInfo.ParameterType.Name | Should -Be 'SwitchParameter' } It 'Should support ShouldProcess' { @@ -177,4 +201,3 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { } } } - From a53f0c61553b0825e66b34dca2059aba23db2075 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 15:40:36 +0100 Subject: [PATCH 27/87] Refactor New-SqlDscDataFile integration tests to validate FileGroup functionality; enhance tests for PassThru and WhatIf scenarios --- .../New-SqlDscDataFile.Integration.Tests.ps1 | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 index b00563d791..0bcbf6c1ef 100644 --- a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 @@ -59,47 +59,51 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 $script:mockFileGroup = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' -Confirm:$false } - It 'Should create a DataFile with FileGroup successfully' { + It 'Should create a DataFile and add it to FileGroup without PassThru' { + $initialFileCount = $script:mockFileGroup.Files.Count + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $result.Name | Should -Be 'TestDataFile' - $result.Parent | Should -Be $script:mockFileGroup + $result | Should -BeNullOrEmpty + $script:mockFileGroup.Files.Count | Should -Be ($initialFileCount + 1) + + $addedFile = $script:mockFileGroup.Files | Where-Object -FilterScript { $_.Name -eq 'TestDataFile' } + $addedFile | Should -Not -BeNullOrEmpty + $addedFile.FileName | Should -Be 'C:\Data\TestDataFile.ndf' } - It 'Should create a DataFile with FileGroup and FileName' { - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile2' -FileName 'C:\Data\TestDataFile2.ndf' -Confirm:$false + It 'Should create a DataFile and return it with PassThru' { + $initialFileCount = $script:mockFileGroup.Files.Count + + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile2' -FileName 'C:\Data\TestDataFile2.ndf' -PassThru -Confirm:$false $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' $result.Name | Should -Be 'TestDataFile2' $result.FileName | Should -Be 'C:\Data\TestDataFile2.ndf' $result.Parent | Should -Be $script:mockFileGroup - } - - It 'Should accept FileGroup parameter from pipeline' { - $result = $script:mockFileGroup | New-SqlDscDataFile -Name 'PipelineDataFile' -FileName 'C:\Data\PipelineDataFile.ndf' -Confirm:$false - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $result.Name | Should -Be 'PipelineDataFile' - $result.Parent | Should -Be $script:mockFileGroup + $script:mockFileGroup.Files.Count | Should -Be ($initialFileCount + 1) } It 'Should support Force parameter to bypass confirmation' { - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'ForcedDataFile' -FileName 'C:\Data\ForcedDataFile.ndf' -Force + $initialFileCount = $script:mockFileGroup.Files.Count + + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'ForcedDataFile' -FileName 'C:\Data\ForcedDataFile.ndf' -PassThru -Force $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' $result.Name | Should -Be 'ForcedDataFile' $result.Parent | Should -Be $script:mockFileGroup + $script:mockFileGroup.Files.Count | Should -Be ($initialFileCount + 1) } - It 'Should return null when user declines confirmation' { + It 'Should not add file when WhatIf is specified' { + $initialFileCount = $script:mockFileGroup.Files.Count + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'DeclinedDataFile' -FileName 'C:\Data\DeclinedDataFile.ndf' -Confirm:$false -WhatIf $result | Should -BeNullOrEmpty + $script:mockFileGroup.Files.Count | Should -Be $initialFileCount } } @@ -113,22 +117,22 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 $script:mockFileGroup = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' -Confirm:$false } - It 'Should allow setting Size property' { - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false + It 'Should allow setting Size property on returned DataFile' { + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -PassThru -Confirm:$false $result.Size = 1024.0 $result.Size | Should -Be 1024.0 } - It 'Should allow setting Growth property' { - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false + It 'Should allow setting Growth property on returned DataFile' { + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -PassThru -Confirm:$false $result.Growth = 64.0 $result.Growth | Should -Be 64.0 } - It 'Should allow setting GrowthType property' { - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false + It 'Should allow setting GrowthType property on returned DataFile' { + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -PassThru -Confirm:$false $result.GrowthType = [Microsoft.SqlServer.Management.Smo.FileGrowthType]::Percent $result.GrowthType | Should -Be 'Percent' From d15f4e708f80abd677dfbe557199be4aa5c980fe Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 15:45:50 +0100 Subject: [PATCH 28/87] Remove deprecated Add-SqlDscDataFile integration test from pipeline configuration --- azure-pipelines.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f7e20284df..daf94fe19e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -296,7 +296,6 @@ stages: 'tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1' 'tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1' - 'tests/Integration/Commands/Add-SqlDscDataFile.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscSetupLog.Integration.Tests.ps1' 'tests/Integration/Commands/Connect-SqlDscDatabaseEngine.Integration.Tests.ps1' 'tests/Integration/Commands/Disconnect-SqlDscDatabaseEngine.Integration.Tests.ps1' From 9030783ce6982de0a40047b94336fa42c3c8263a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 15:46:03 +0100 Subject: [PATCH 29/87] Refactor Add-SqlDscFileGroup integration tests to automatically create and validate DataFile addition to FileGroup --- .../Add-SqlDscFileGroup.Integration.Tests.ps1 | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 index 62a430c822..f87fd13993 100644 --- a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 @@ -137,18 +137,17 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 # Create FileGroup $fileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'SecondaryFileGroup' -Confirm:$false - # Create DataFile - $dataFile = New-SqlDscDataFile -FileGroup $fileGroup -Name 'SecondaryDataFile' -FileName 'C:\Data\SecondaryDataFile.ndf' -Confirm:$false - - # Add DataFile to FileGroup - Add-SqlDscDataFile -FileGroup $fileGroup -DataFile $dataFile + # Create DataFile - it will be automatically added to the FileGroup + $null = New-SqlDscDataFile -FileGroup $fileGroup -Name 'SecondaryDataFile' -FileName 'C:\Data\SecondaryDataFile.ndf' -Confirm:$false # Add FileGroup to Database Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $fileGroup # Verify structure $script:testDatabase.FileGroups[$fileGroup.Name] | Should -Be $fileGroup - $script:testDatabase.FileGroups[$fileGroup.Name].Files[$dataFile.Name] | Should -Be $dataFile + $addedFile = $script:testDatabase.FileGroups[$fileGroup.Name].Files | Where-Object -FilterScript { $_.Name -eq 'SecondaryDataFile' } + $addedFile | Should -Not -BeNullOrEmpty + $addedFile.FileName | Should -Be 'C:\Data\SecondaryDataFile.ndf' } } } From d410342a1437450a7d4c2161f8038f6035ded5d4 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 15:49:14 +0100 Subject: [PATCH 30/87] Remove `Add-SqlDscDataFile` command from changelog; deprecated functionality --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ee02f1d40..1e3ae183fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,10 +39,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 specified physical file paths, supporting both regular database files (.mdf, .ndf) and sparse files for database snapshots (.ss). The `FileGroup` parameter is mandatory, requiring DataFile objects to be created with an associated FileGroup. -- Added public command `Add-SqlDscDataFile` to add one or more existing DataFile - objects to a FileGroup. This command provides a clean way to associate DataFile - objects with FileGroups after they have been created. The command supports - accepting multiple DataFile objects as an array or through pipeline input. - Added public command `Add-SqlDscFileGroup` to add one or more FileGroup objects to a Database. This command provides a clean way to associate FileGroup objects with a Database after they have been created. From 2b13c4e86054345d00ae352ff0eaa80ef18b46fd Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 15:52:19 +0100 Subject: [PATCH 31/87] Add integration tests for creating a database with custom file groups and data files --- .../New-SqlDscDatabase.Integration.Tests.ps1 | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 index 7c79fb42dc..3515e82bd5 100644 --- a/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 @@ -150,4 +150,66 @@ Describe 'New-SqlDscDatabase' -Tag @('Integration_SQL2017', 'Integration_SQL2019 Should -Throw } } + + Context 'When creating a database with file groups' { + BeforeAll { + $script:testDatabaseWithFileGroups = 'SqlDscTestDbFileGroups_' + (Get-Random) + + # Get the default data directory from the server + $script:dataDirectory = $script:serverObject.Settings.DefaultFile + + if (-not $script:dataDirectory) + { + $script:dataDirectory = $script:serverObject.Information.MasterDBPath + } + + # Ensure the directory exists + if (-not (Test-Path -Path $script:dataDirectory)) + { + $null = New-Item -Path $script:dataDirectory -ItemType Directory -Force + } + } + + AfterAll { + # Clean up test database + $dbToRemove = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseWithFileGroups -ErrorAction 'SilentlyContinue' + if ($dbToRemove) + { + $null = Remove-SqlDscDatabase -DatabaseObject $dbToRemove -Force -ErrorAction 'Stop' + } + } + + It 'Should create a database with custom file groups and data files' { + # Create PRIMARY filegroup with data file + $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' + $primaryFilePath = Join-Path -Path $script:dataDirectory -ChildPath ($script:testDatabaseWithFileGroups + '_Primary.mdf') + $null = New-SqlDscDataFile -FileGroup $primaryFileGroup -Name ($script:testDatabaseWithFileGroups + '_Primary') -FileName $primaryFilePath -Force + + # Create a secondary filegroup with data file + $secondaryFileGroup = New-SqlDscFileGroup -Name 'SecondaryFG' + $secondaryFilePath = Join-Path -Path $script:dataDirectory -ChildPath ($script:testDatabaseWithFileGroups + '_Secondary.ndf') + $null = New-SqlDscDataFile -FileGroup $secondaryFileGroup -Name ($script:testDatabaseWithFileGroups + '_Secondary') -FileName $secondaryFilePath -Force + + # Create database with file groups + $result = New-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseWithFileGroups -FileGroup @($primaryFileGroup, $secondaryFileGroup) -Force -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be $script:testDatabaseWithFileGroups + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Database' + + # Verify the database exists with correct file groups + $createdDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseWithFileGroups -Refresh -ErrorAction 'Stop' + $createdDb | Should -Not -BeNullOrEmpty + + # Verify PRIMARY filegroup exists + $createdDb.FileGroups['PRIMARY'] | Should -Not -BeNullOrEmpty + $createdDb.FileGroups['PRIMARY'].Files.Count | Should -Be 1 + $createdDb.FileGroups['PRIMARY'].Files[0].Name | Should -Be ($script:testDatabaseWithFileGroups + '_Primary') + + # Verify secondary filegroup exists + $createdDb.FileGroups['SecondaryFG'] | Should -Not -BeNullOrEmpty + $createdDb.FileGroups['SecondaryFG'].Files.Count | Should -Be 1 + $createdDb.FileGroups['SecondaryFG'].Files[0].Name | Should -Be ($script:testDatabaseWithFileGroups + '_Secondary') + } + } } From 0fdef27ed79d1e8617337ec24f5087e416f523b8 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 16:05:57 +0100 Subject: [PATCH 32/87] Refactor example in New-SqlDscDatabase to use New-SqlDscFileGroup and New-SqlDscDataFile for improved clarity and functionality --- source/Public/New-SqlDscDatabase.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Public/New-SqlDscDatabase.ps1 b/source/Public/New-SqlDscDatabase.ps1 index 57a63bff0b..3c9a7b0ec2 100644 --- a/source/Public/New-SqlDscDatabase.ps1 +++ b/source/Public/New-SqlDscDatabase.ps1 @@ -78,8 +78,8 @@ .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' $sourceDb = $serverObject.Databases['MyDatabase'] - $fileGroup = New-Object Microsoft.SqlServer.Management.Smo.FileGroup -ArgumentList $sourceDb, 'PRIMARY' - $dataFile = New-Object Microsoft.SqlServer.Management.Smo.DataFile -ArgumentList $fileGroup, 'MyDatabase_Data', 'C:\Snapshots\MyDatabase_Data.ss' + $fileGroup = New-SqlDscFileGroup -Database $sourceDb -Name 'PRIMARY' + $dataFile = New-SqlDscDataFile -FileGroup $fileGroup -Name 'MyDatabase_Data' -Path 'C:\Snapshots\MyDatabase_Data.ss' $fileGroup.Files.Add($dataFile) $serverObject | New-SqlDscDatabase -Name 'MyDatabaseSnapshot' -DatabaseSnapshotBaseName 'MyDatabase' -FileGroup @($fileGroup) -Force From 2abd5ef808d2534aff1042d5fd882d83fb2ee595 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 17:02:45 +0100 Subject: [PATCH 33/87] Refactor New-SqlDscDataFile function to streamline process handling and improve code readability --- source/Public/New-SqlDscDataFile.ps1 | 29 +++++++++++++--------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index f4cd4b63cd..5314522fe8 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -85,27 +85,24 @@ function New-SqlDscDataFile $Force ) - process + if ($Force.IsPresent -and -not $Confirm) { - if ($Force.IsPresent -and -not $Confirm) - { - $ConfirmPreference = 'None' - } + $ConfirmPreference = 'None' + } - $descriptionMessage = $script:localizedData.DataFile_Create_ShouldProcessDescription -f $Name, $FileGroup.Name - $confirmationMessage = $script:localizedData.DataFile_Create_ShouldProcessConfirmation -f $Name - $captionMessage = $script:localizedData.DataFile_Create_ShouldProcessCaption + $descriptionMessage = $script:localizedData.DataFile_Create_ShouldProcessDescription -f $Name, $FileGroup.Name + $confirmationMessage = $script:localizedData.DataFile_Create_ShouldProcessConfirmation -f $Name + $captionMessage = $script:localizedData.DataFile_Create_ShouldProcessCaption - if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) - { - $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroup, $Name, $FileName) + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroup, $Name, $FileName) - $FileGroup.Files.Add($dataFileObject) + $FileGroup.Files.Add($dataFileObject) - if ($PassThru.IsPresent) - { - return $dataFileObject - } + if ($PassThru.IsPresent) + { + return $dataFileObject } } } From 6dcd22e73654afa5ef3b617d712a550395f2a161 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 2 Nov 2025 18:54:56 +0100 Subject: [PATCH 34/87] Refactor New-SqlDscDataFile integration tests to use BeforeEach for setup of DataFile properties --- .../Commands/New-SqlDscDataFile.Integration.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 index 0bcbf6c1ef..299d0337b1 100644 --- a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 @@ -108,7 +108,7 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 } Context 'When verifying DataFile properties' { - BeforeAll { + BeforeEach { # Create a real SMO Database object $script:mockDatabase = [Microsoft.SqlServer.Management.Smo.Database]::new() $script:mockDatabase.Name = 'TestDatabase' From b0751ce74b8c107d3bb11902c55a9616f3509597 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 08:11:21 +0100 Subject: [PATCH 35/87] Add terminal profile settings for Copilot in VSCode configuration --- .vscode/settings.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 14bbda0996..03c5688811 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -173,5 +173,26 @@ "path": "bash", "args": [] } + }, + "chat.tools.terminal.terminalProfile.osx": { + "path": "pwsh", // bash instead of zsh + "args": [], // non-login instead of login on macOS + "env": { + "COPILOT": "1" // environment variable that can be used in init scripts + } + }, + "chat.tools.terminal.terminalProfile.linux": { + "path": "pwsh", + "args": [], + "env": { + "COPILOT": "1" + } + }, + "chat.tools.terminal.terminalProfile.windows": { + "path": "pwsh.exe", + "args": [], + "env": { + "COPILOT": "1" + } } } From c7df4948a1f3a18dfc0047003ed7acf954028006 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 10:14:30 +0100 Subject: [PATCH 36/87] Add unit tests for DatabaseFileSpec and conversion functions - Created tests for DatabaseFileSpec class to validate instantiation, property setting, and default values. - Implemented tests for ConvertTo-SqlDscDataFile and ConvertTo-SqlDscFileGroup functions to ensure correct conversion from spec objects to SMO types. - Updated New-SqlDscDatabase tests to support new parameter sets and validate file group specifications. - Modified New-SqlDscFileGroup tests to remove pipeline input for Database parameter and ensure correct parameter set handling. - Enhanced SMO stub classes to include additional properties for testing. --- source/Classes/002.DatabaseFileSpec.ps1 | 97 +++++++ source/Classes/004.DatabaseFileGroupSpec.ps1 | 110 ++++++++ source/Public/ConvertTo-SqlDscDataFile.ps1 | 73 +++++ source/Public/ConvertTo-SqlDscFileGroup.ps1 | 68 +++++ source/Public/New-SqlDscDataFile.ps1 | 126 ++++++++- source/Public/New-SqlDscDatabase.ps1 | 45 ++- source/Public/New-SqlDscFileGroup.ps1 | 163 ++++++++--- .../New-SqlDscDatabase.Integration.Tests.ps1 | 16 +- .../Classes/DatabaseFileGroupSpec.Tests.ps1 | 257 ++++++++++++++++++ tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 | 173 ++++++++++++ .../Public/ConvertTo-SqlDscDataFile.Tests.ps1 | 189 +++++++++++++ .../ConvertTo-SqlDscFileGroup.Tests.ps1 | 207 ++++++++++++++ .../Unit/Public/New-SqlDscDataFile.Tests.ps1 | 7 +- .../Unit/Public/New-SqlDscDatabase.Tests.ps1 | 96 ++++++- .../Unit/Public/New-SqlDscFileGroup.Tests.ps1 | 31 +-- tests/Unit/Stubs/SMO.cs | 7 + 16 files changed, 1574 insertions(+), 91 deletions(-) create mode 100644 source/Classes/002.DatabaseFileSpec.ps1 create mode 100644 source/Classes/004.DatabaseFileGroupSpec.ps1 create mode 100644 source/Public/ConvertTo-SqlDscDataFile.ps1 create mode 100644 source/Public/ConvertTo-SqlDscFileGroup.ps1 create mode 100644 tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 create mode 100644 tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 create mode 100644 tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 create mode 100644 tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 diff --git a/source/Classes/002.DatabaseFileSpec.ps1 b/source/Classes/002.DatabaseFileSpec.ps1 new file mode 100644 index 0000000000..c8437922a7 --- /dev/null +++ b/source/Classes/002.DatabaseFileSpec.ps1 @@ -0,0 +1,97 @@ +<# + .SYNOPSIS + Defines a data file specification for a database file group. + + .DESCRIPTION + This class represents a data file specification that can be used when + creating a new database. It contains the properties needed to define + a data file without requiring an existing database or file group SMO object. + + .PARAMETER Name + The logical name of the data file. + + .PARAMETER FileName + The physical file path for the data file. This must be a valid path + on the SQL Server instance. + + .PARAMETER Size + The initial size of the data file in kilobytes. If not specified, + SQL Server will use its default initial size. + + .PARAMETER MaxSize + The maximum size to which the data file can grow in kilobytes. + If not specified, the file can grow without limit (or up to disk space). + + .PARAMETER Growth + The amount by which the data file grows when it needs more space. + The value is in kilobytes if GrowthType is KB, or a percentage if + GrowthType is Percent. If not specified, SQL Server will use its + default growth setting. + + .PARAMETER GrowthType + Specifies whether the Growth value is in kilobytes (KB) or percent (Percent). + If not specified, defaults to KB. + + .PARAMETER IsPrimaryFile + Specifies whether this file is the primary file in the PRIMARY file group. + Only one file in the PRIMARY file group should be marked as the primary file. + This property is typically used for the first file in the PRIMARY file group. + + .NOTES + This class is used to specify data file configurations when creating a new + database via New-SqlDscDatabase. Unlike SMO DataFile objects, these + specification objects can be created without an existing database context. + + .EXAMPLE + $fileSpec = [DatabaseFileSpec]::new() + $fileSpec.Name = 'MyDatabase_Data' + $fileSpec.FileName = 'C:\SQLData\MyDatabase.mdf' + $fileSpec.Size = 102400 # 100 MB in KB + $fileSpec.Growth = 10240 # 10 MB in KB + $fileSpec.GrowthType = 'KB' + + Creates a new data file specification with a specific size and growth settings. + + .EXAMPLE + [DatabaseFileSpec] @{ + Name = 'MyDatabase_Data' + FileName = 'C:\SQLData\MyDatabase.mdf' + IsPrimaryFile = $true + } + + Creates a new primary data file specification using hashtable syntax. +#> +class DatabaseFileSpec +{ + [System.String] + $Name + + [System.String] + $FileName + + [System.Nullable[System.Double]] + $Size + + [System.Nullable[System.Double]] + $MaxSize + + [System.Nullable[System.Double]] + $Growth + + [ValidateSet('KB', 'MB', 'Percent')] + [System.String] + $GrowthType + + [System.Boolean] + $IsPrimaryFile = $false + + DatabaseFileSpec() + { + } + + DatabaseFileSpec([System.String] $name, [System.String] $fileName) + { + $this.Name = $name + $this.FileName = $fileName + } +} diff --git a/source/Classes/004.DatabaseFileGroupSpec.ps1 b/source/Classes/004.DatabaseFileGroupSpec.ps1 new file mode 100644 index 0000000000..83389c674a --- /dev/null +++ b/source/Classes/004.DatabaseFileGroupSpec.ps1 @@ -0,0 +1,110 @@ +<# + .SYNOPSIS + Defines a file group specification for a database. + + .DESCRIPTION + This class represents a file group specification that can be used when + creating a new database. It contains the properties needed to define + a file group and its associated data files without requiring an existing + database SMO object. + + .PARAMETER Name + The name of the file group. For the primary file group, this should be 'PRIMARY'. + + .PARAMETER Files + An array of DatabaseFileSpec objects that define the data files belonging + to this file group. At least one file must be specified for each file group. + + .PARAMETER ReadOnly + Specifies whether the file group is read-only. If not specified, defaults + to $false (read-write). + + .PARAMETER IsDefault + Specifies whether this file group should be the default file group for + new objects. If not specified, defaults to $false. Typically, only the + PRIMARY file group or one custom file group should be marked as default. + + .NOTES + This class is used to specify file group configurations when creating a new + database via New-SqlDscDatabase. Unlike SMO FileGroup objects, these + specification objects can be created without an existing database context. + + When creating a database, you typically need at least one file group named + 'PRIMARY' which contains the primary data file. Additional file groups can + be added for organizing data files. + + .EXAMPLE + $primaryFile = [DatabaseFileSpec] @{ + Name = 'MyDatabase_Primary' + FileName = 'C:\SQLData\MyDatabase.mdf' + IsPrimaryFile = $true + } + + $primaryFileGroup = [DatabaseFileGroupSpec]::new() + $primaryFileGroup.Name = 'PRIMARY' + $primaryFileGroup.Files = @($primaryFile) + + Creates a PRIMARY file group specification with one primary data file. + + .EXAMPLE + $dataFile1 = [DatabaseFileSpec] @{ + Name = 'MyDatabase_Data1' + FileName = 'D:\SQLData\MyDatabase_Data1.ndf' + Size = 204800 # 200 MB + } + + $dataFile2 = [DatabaseFileSpec] @{ + Name = 'MyDatabase_Data2' + FileName = 'D:\SQLData\MyDatabase_Data2.ndf' + Size = 204800 # 200 MB + } + + $secondaryFileGroup = [DatabaseFileGroupSpec] @{ + Name = 'SECONDARY' + Files = @($dataFile1, $dataFile2) + } + + Creates a SECONDARY file group specification with two data files. + + .EXAMPLE + [DatabaseFileGroupSpec] @{ + Name = 'PRIMARY' + Files = @( + [DatabaseFileSpec] @{ + Name = 'MyDB_Primary' + FileName = 'C:\SQLData\MyDB.mdf' + } + ) + } + + Creates a PRIMARY file group using hashtable syntax with an embedded file spec. +#> +class DatabaseFileGroupSpec +{ + [System.String] + $Name + + [DatabaseFileSpec[]] + $Files + + [System.Boolean] + $ReadOnly = $false + + [System.Boolean] + $IsDefault = $false + + DatabaseFileGroupSpec() + { + } + + DatabaseFileGroupSpec([System.String] $name) + { + $this.Name = $name + } + + DatabaseFileGroupSpec([System.String] $name, [DatabaseFileSpec[]] $files) + { + $this.Name = $name + $this.Files = $files + } +} diff --git a/source/Public/ConvertTo-SqlDscDataFile.ps1 b/source/Public/ConvertTo-SqlDscDataFile.ps1 new file mode 100644 index 0000000000..46f0ebbfed --- /dev/null +++ b/source/Public/ConvertTo-SqlDscDataFile.ps1 @@ -0,0 +1,73 @@ +<# + .SYNOPSIS + Converts a DatabaseFileSpec object to a SMO DataFile object. + + .DESCRIPTION + This command takes a DatabaseFileSpec specification object and converts it + to a SMO (SQL Server Management Objects) DataFile object. This is used + internally when creating databases with custom file configurations. + + .PARAMETER FileGroupObject + The SMO FileGroup object to which the DataFile will belong. + + .PARAMETER DataFileSpec + The DatabaseFileSpec object containing the data file configuration. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.DataFile + + .EXAMPLE + $fileSpec = New-SqlDscDataFile -Name 'TestDB_Data' -FileName 'C:\SQLData\TestDB.mdf' -AsSpec + $fileGroup = New-Object Microsoft.SqlServer.Management.Smo.FileGroup($database, 'PRIMARY') + $dataFile = ConvertTo-SqlDscDataFile -FileGroupObject $fileGroup -DataFileSpec $fileSpec + + Converts a DatabaseFileSpec to a SMO DataFile object. +#> +function ConvertTo-SqlDscDataFile +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [CmdletBinding()] + [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.FileGroup] + $FileGroupObject, + + [Parameter(Mandatory = $true)] + [DatabaseFileSpec] + $DataFileSpec + ) + + # Create SMO DataFile object + $smoDataFile = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroupObject, $DataFileSpec.Name) + $smoDataFile.FileName = $DataFileSpec.FileName + + # Set optional data file properties + if ($DataFileSpec.Size -gt 0) + { + $smoDataFile.Size = $DataFileSpec.Size + } + + if ($DataFileSpec.MaxSize -gt 0) + { + $smoDataFile.MaxSize = $DataFileSpec.MaxSize + } + + if ($DataFileSpec.Growth -gt 0) + { + $smoDataFile.Growth = $DataFileSpec.Growth + } + + if ($DataFileSpec.GrowthType) + { + $smoDataFile.GrowthType = $DataFileSpec.GrowthType + } + + if ($DataFileSpec.IsPrimaryFile) + { + $smoDataFile.IsPrimaryFile = $DataFileSpec.IsPrimaryFile + } + + return $smoDataFile +} diff --git a/source/Public/ConvertTo-SqlDscFileGroup.ps1 b/source/Public/ConvertTo-SqlDscFileGroup.ps1 new file mode 100644 index 0000000000..0e1de42bba --- /dev/null +++ b/source/Public/ConvertTo-SqlDscFileGroup.ps1 @@ -0,0 +1,68 @@ +<# + .SYNOPSIS + Converts a DatabaseFileGroupSpec object to a SMO FileGroup object. + + .DESCRIPTION + This command takes a DatabaseFileGroupSpec specification object and converts it + to a SMO (SQL Server Management Objects) FileGroup object with all configured + data files. This is used internally when creating databases with custom file + group configurations. + + .PARAMETER DatabaseObject + The SMO Database object to which the FileGroup will belong. + + .PARAMETER FileGroupSpec + The DatabaseFileGroupSpec object containing the file group configuration. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.FileGroup + + .EXAMPLE + $fileSpec = New-SqlDscDataFile -Name 'TestDB_Data' -FileName 'C:\SQLData\TestDB.mdf' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($fileSpec) -AsSpec + $smoFileGroup = ConvertTo-SqlDscFileGroup -DatabaseObject $database -FileGroupSpec $fileGroupSpec + + Converts a DatabaseFileGroupSpec to a SMO FileGroup object with data files. +#> +function ConvertTo-SqlDscFileGroup +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [CmdletBinding()] + [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup])] + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Database] + $DatabaseObject, + + [Parameter(Mandatory = $true)] + [DatabaseFileGroupSpec] + $FileGroupSpec + ) + + # Create SMO FileGroup object + $smoFileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new($DatabaseObject, $FileGroupSpec.Name) + + # Set file group properties + if ($null -ne $FileGroupSpec.ReadOnly) + { + $smoFileGroup.ReadOnly = $FileGroupSpec.ReadOnly + } + + if ($null -ne $FileGroupSpec.IsDefault) + { + $smoFileGroup.IsDefault = $FileGroupSpec.IsDefault + } + + # Add data files to the file group + if ($FileGroupSpec.Files -and $FileGroupSpec.Files.Count -gt 0) + { + foreach ($fileSpec in $FileGroupSpec.Files) + { + $smoDataFile = ConvertTo-SqlDscDataFile -FileGroupObject $smoFileGroup -DataFileSpec $fileSpec + $smoFileGroup.Files.Add($smoDataFile) + } + } + + return $smoFileGroup +} diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index 5314522fe8..c2726f3893 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -20,6 +20,16 @@ For regular databases, this should be the data file path (typically with .mdf or .ndf extension). + .PARAMETER DataFileSpec + Specifies a DatabaseFileSpec object that defines the data file configuration + including name, file path, size, growth, and other properties. + + .PARAMETER AsSpec + Returns a DatabaseFileSpec object instead of a SMO DataFile object. + This specification object can be used with New-SqlDscFileGroup -AsSpec + or passed directly to New-SqlDscDatabase to define data files before + the database is created. + .PARAMETER PassThru Returns the DataFile object that was created and added to the FileGroup. @@ -52,35 +62,81 @@ Creates an additional DataFile and returns it for further processing. + .EXAMPLE + $dataFileSpec = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -AsSpec + + Creates a DatabaseFileSpec object that can be used with New-SqlDscFileGroup -AsSpec + or passed to New-SqlDscDatabase. + + .EXAMPLE + $dataFileSpec = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -Size 102400 -MaxSize 5242880 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile $true -AsSpec + + Creates a DatabaseFileSpec object with all properties set directly via parameters. + .OUTPUTS None. Unless -PassThru is specified, in which case it returns `[Microsoft.SqlServer.Management.Smo.DataFile]`. #> function New-SqlDscDataFile { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] - [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [CmdletBinding(DefaultParameterSetName = 'Standard', SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([Microsoft.SqlServer.Management.Smo.DataFile])] + [OutputType([DatabaseFileSpec])] param ( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Standard')] + [Parameter(Mandatory = $true, ParameterSetName = 'FromSpec')] [Microsoft.SqlServer.Management.Smo.FileGroup] $FileGroup, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Standard')] + [Parameter(Mandatory = $true, ParameterSetName = 'AsSpec')] [ValidateNotNullOrEmpty()] [System.String] $Name, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Standard')] + [Parameter(Mandatory = $true, ParameterSetName = 'AsSpec')] [ValidateNotNullOrEmpty()] [System.String] $FileName, - [Parameter()] + [Parameter(Mandatory = $true, ParameterSetName = 'FromSpec')] + [System.Object] + $DataFileSpec, + + [Parameter(Mandatory = $true, ParameterSetName = 'AsSpec')] + [System.Management.Automation.SwitchParameter] + $AsSpec, + + [Parameter(ParameterSetName = 'AsSpec')] + [System.Nullable[System.Double]] + $Size, + + [Parameter(ParameterSetName = 'AsSpec')] + [System.Nullable[System.Double]] + $MaxSize, + + [Parameter(ParameterSetName = 'AsSpec')] + [System.Nullable[System.Double]] + $Growth, + + [Parameter(ParameterSetName = 'AsSpec')] + [ValidateSet('KB', 'MB', 'Percent')] + [System.String] + $GrowthType, + + [Parameter(ParameterSetName = 'AsSpec')] + [System.Boolean] + $IsPrimaryFile, + + [Parameter(ParameterSetName = 'Standard')] + [Parameter(ParameterSetName = 'FromSpec')] [System.Management.Automation.SwitchParameter] $PassThru, - [Parameter()] + [Parameter(ParameterSetName = 'Standard')] + [Parameter(ParameterSetName = 'FromSpec')] [System.Management.Automation.SwitchParameter] $Force ) @@ -90,13 +146,65 @@ function New-SqlDscDataFile $ConfirmPreference = 'None' } - $descriptionMessage = $script:localizedData.DataFile_Create_ShouldProcessDescription -f $Name, $FileGroup.Name - $confirmationMessage = $script:localizedData.DataFile_Create_ShouldProcessConfirmation -f $Name + if ($PSCmdlet.ParameterSetName -eq 'AsSpec') + { + $fileSpec = [DatabaseFileSpec]::new($Name, $FileName) + + if ($PSBoundParameters.ContainsKey('Size')) + { + $fileSpec.Size = $Size + } + + if ($PSBoundParameters.ContainsKey('MaxSize')) + { + $fileSpec.MaxSize = $MaxSize + } + + if ($PSBoundParameters.ContainsKey('Growth')) + { + $fileSpec.Growth = $Growth + } + + if ($PSBoundParameters.ContainsKey('GrowthType')) + { + $fileSpec.GrowthType = $GrowthType + } + + if ($PSBoundParameters.ContainsKey('IsPrimaryFile')) + { + $fileSpec.IsPrimaryFile = $IsPrimaryFile + } + + return $fileSpec + } + + # Determine the data file name based on parameter set + $dataFileName = if ($PSCmdlet.ParameterSetName -eq 'FromSpec') + { + $DataFileSpec.Name + } + else + { + $Name + } + + $descriptionMessage = $script:localizedData.DataFile_Create_ShouldProcessDescription -f $dataFileName, $FileGroup.Name + $confirmationMessage = $script:localizedData.DataFile_Create_ShouldProcessConfirmation -f $dataFileName $captionMessage = $script:localizedData.DataFile_Create_ShouldProcessCaption if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) { - $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroup, $Name, $FileName) + if ($PSCmdlet.ParameterSetName -eq 'FromSpec') + { + Write-Verbose -Message (' Creating data file: {0}' -f $DataFileSpec.Name) + + # Use the spec object's method to create the SMO DataFile + $dataFileObject = $DataFileSpec.ToSmoDataFile($FileGroup) + } + else + { + $dataFileObject = [Microsoft.SqlServer.Management.Smo.DataFile]::new($FileGroup, $Name, $FileName) + } $FileGroup.Files.Add($dataFileObject) diff --git a/source/Public/New-SqlDscDatabase.ps1 b/source/Public/New-SqlDscDatabase.ps1 index 3c9a7b0ec2..f2af623c7a 100644 --- a/source/Public/New-SqlDscDatabase.ps1 +++ b/source/Public/New-SqlDscDatabase.ps1 @@ -39,11 +39,17 @@ of a regular database. The snapshot name is specified in the Name parameter. .PARAMETER FileGroup - Specifies an array of FileGroup objects to add to the database. - Each FileGroup can contain DataFile objects with FileName properties - specifying the file paths. For database snapshots, the FileName must - point to sparse file locations. For regular databases, this allows - custom file and filegroup configuration. + Specifies an array of DatabaseFileGroupSpec objects that define the file groups + and data files for the database. Each DatabaseFileGroupSpec contains the file group + name and an array of DatabaseFileSpec objects for the data files. + + This parameter allows you to specify custom file and filegroup configurations + before the database is created, avoiding the SMO limitation where DataFile objects + require an existing database context. + + For database snapshots, the FileName in each DatabaseFileSpec must point to sparse + file locations. For regular databases, this allows full control over PRIMARY and + secondary file group configurations. .PARAMETER Force Specifies that the database should be created without any confirmation. @@ -77,14 +83,18 @@ .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - $sourceDb = $serverObject.Databases['MyDatabase'] - $fileGroup = New-SqlDscFileGroup -Database $sourceDb -Name 'PRIMARY' - $dataFile = New-SqlDscDataFile -FileGroup $fileGroup -Name 'MyDatabase_Data' -Path 'C:\Snapshots\MyDatabase_Data.ss' - $fileGroup.Files.Add($dataFile) - $serverObject | New-SqlDscDatabase -Name 'MyDatabaseSnapshot' -DatabaseSnapshotBaseName 'MyDatabase' -FileGroup @($fileGroup) -Force - Creates a database snapshot named **MyDatabaseSnapshot** from the source database **MyDatabase** - with a specified sparse file location without prompting for confirmation. + $primaryFile = New-SqlDscDataFile -Name 'MyDatabase_Primary' -FileName 'D:\SQLData\MyDatabase.mdf' -Size 102400 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile $true -AsSpec + $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault $true -AsSpec + + $secondaryFile = New-SqlDscDataFile -Name 'MyDatabase_Secondary' -FileName 'E:\SQLData\MyDatabase.ndf' -Size 204800 -AsSpec + $secondaryFileGroup = New-SqlDscFileGroup -Name 'SECONDARY' -Files @($secondaryFile) -AsSpec + + $serverObject | New-SqlDscDatabase -Name 'MyDatabase' -FileGroup @($primaryFileGroup, $secondaryFileGroup) -Force + + Creates a new database named **MyDatabase** with custom PRIMARY and SECONDARY file groups + using specification objects created with the -AsSpec parameter. All properties are set + directly via parameters without prompting for confirmation. .OUTPUTS `[Microsoft.SqlServer.Management.Smo.Database]` @@ -134,7 +144,7 @@ function New-SqlDscDatabase $DatabaseSnapshotBaseName, [Parameter()] - [Microsoft.SqlServer.Management.Smo.FileGroup[]] + [DatabaseFileGroupSpec[]] $FileGroup, [Parameter()] @@ -307,7 +317,14 @@ function New-SqlDscDatabase # Add FileGroups if provided (applies to both regular databases and snapshots) if ($PSBoundParameters.ContainsKey('FileGroup')) { - Add-SqlDscFileGroup -Database $sqlDatabaseObjectToCreate -FileGroup $FileGroup + foreach ($fileGroupSpec in $FileGroup) + { + # Create FileGroup using New-SqlDscFileGroup with spec object + $smoFileGroup = New-SqlDscFileGroup -Database $sqlDatabaseObjectToCreate -FileGroupSpec $fileGroupSpec -Force + + # Add the file group to the database + Add-SqlDscFileGroup -Database $sqlDatabaseObjectToCreate -FileGroup $smoFileGroup + } } Write-Verbose -Message ($script:localizedData.Database_Creating -f $Name) diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index d00aa74729..2e54e70123 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -16,6 +16,28 @@ .PARAMETER Name Specifies the name of the FileGroup to create. + .PARAMETER FileGroupSpec + Specifies a DatabaseFileGroupSpec object that defines the file group configuration + including name, properties, and data files. + + .PARAMETER AsSpec + Returns a DatabaseFileGroupSpec object instead of a SMO FileGroup object. + This specification object can be passed to New-SqlDscDatabase to define + file groups before the database is created. + + .PARAMETER Files + Specifies an array of DatabaseFileSpec objects to include in the file group. + Only valid when using -AsSpec. + + .PARAMETER ReadOnly + Specifies whether the file group should be read-only. Only valid when using -AsSpec. + + .PARAMETER IsDefault + Specifies whether this file group should be the default file group. Only valid when using -AsSpec. + + .PARAMETER PassThru + Returns the created FileGroup object. By default, this cmdlet does not generate any output. + .PARAMETER Force Specifies that the FileGroup object should be created without prompting for confirmation. By default, the command prompts for confirmation when the Database @@ -31,9 +53,9 @@ .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' $database = $serverObject.Databases['MyDatabase'] - $fileGroup = $database | New-SqlDscFileGroup -Name 'PRIMARY' + $fileGroup = New-SqlDscFileGroup -Database $database -Name 'PRIMARY' - Creates a new PRIMARY FileGroup using pipeline input. + Creates a new PRIMARY FileGroup for the specified database. .EXAMPLE $fileGroup = New-SqlDscFileGroup -Name 'MyFileGroup' @@ -42,73 +64,152 @@ Creates a standalone FileGroup that can be added to a Database later. + .EXAMPLE + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -AsSpec + + Creates a DatabaseFileGroupSpec object that can be passed to New-SqlDscDatabase. + + .EXAMPLE + $primaryFile = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -Size 102400 -IsPrimaryFile $true -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault $true -AsSpec + + Creates a DatabaseFileGroupSpec object with files and properties set directly via parameters. + .OUTPUTS - `[Microsoft.SqlServer.Management.Smo.FileGroup]` + `[Microsoft.SqlServer.Management.Smo.FileGroup]` or `[DatabaseFileGroupSpec]` if -AsSpec is specified. #> function New-SqlDscFileGroup { - [CmdletBinding(DefaultParameterSetName = 'Standalone', SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [CmdletBinding(DefaultParameterSetName = 'AsSpec', SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup])] + [OutputType([DatabaseFileGroupSpec])] param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'WithDatabase')] + [Parameter(Mandatory = $true, ParameterSetName = 'WithDatabase')] + [Parameter(Mandatory = $true, ParameterSetName = 'WithDatabaseFromSpec')] [Microsoft.SqlServer.Management.Smo.Database] $Database, [Parameter(Mandatory = $true, ParameterSetName = 'WithDatabase')] - [Parameter(Mandatory = $true, ParameterSetName = 'Standalone')] + [Parameter(Mandatory = $true, ParameterSetName = 'AsSpec')] [ValidateNotNullOrEmpty()] [System.String] $Name, + [Parameter(Mandatory = $true, ParameterSetName = 'WithDatabaseFromSpec')] + [System.Object] + $FileGroupSpec, + + [Parameter(ParameterSetName = 'AsSpec')] + [System.Management.Automation.SwitchParameter] + $AsSpec, + + [Parameter(ParameterSetName = 'AsSpec')] + [DatabaseFileSpec[]] + $Files, + + [Parameter(ParameterSetName = 'AsSpec')] + [System.Boolean] + $ReadOnly, + + [Parameter(ParameterSetName = 'AsSpec')] + [System.Boolean] + $IsDefault, + + [Parameter(ParameterSetName = 'AsSpec')] + [Parameter(ParameterSetName = 'WithDatabase')] + [Parameter(ParameterSetName = 'WithDatabaseFromSpec')] + [System.Management.Automation.SwitchParameter] + $PassThru, + [Parameter(ParameterSetName = 'WithDatabase')] + [Parameter(ParameterSetName = 'WithDatabaseFromSpec')] [System.Management.Automation.SwitchParameter] $Force ) - process + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + $fileGroupObject = $null + + if ($PSCmdlet.ParameterSetName -in @('WithDatabase', 'WithDatabaseFromSpec')) { - if ($Force.IsPresent -and -not $Confirm) + if (-not $Database.Parent) + { + $errorMessage = $script:localizedData.FileGroup_DatabaseMissingServerObject + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $errorMessage, + 'NSDFG0003', + [System.Management.Automation.ErrorCategory]::InvalidArgument, + $Database + ) + ) + } + + $serverObject = $Database.Parent + + # Determine the file group name based on parameter set + $fileGroupName = if ($PSCmdlet.ParameterSetName -eq 'WithDatabaseFromSpec') + { + $FileGroupSpec.Name + } + else { - $ConfirmPreference = 'None' + $Name } - $fileGroupObject = $null + $descriptionMessage = $script:localizedData.FileGroup_Create_ShouldProcessDescription -f $fileGroupName, $Database.Name, $serverObject.InstanceName + $confirmationMessage = $script:localizedData.FileGroup_Create_ShouldProcessConfirmation -f $fileGroupName + $captionMessage = $script:localizedData.FileGroup_Create_ShouldProcessCaption - if ($PSCmdlet.ParameterSetName -eq 'WithDatabase') + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) { - if (-not $Database.Parent) + if ($PSCmdlet.ParameterSetName -eq 'WithDatabaseFromSpec') { - $errorMessage = $script:localizedData.FileGroup_DatabaseMissingServerObject - - $PSCmdlet.ThrowTerminatingError( - [System.Management.Automation.ErrorRecord]::new( - $errorMessage, - 'NSDFG0003', - [System.Management.Automation.ErrorCategory]::InvalidArgument, - $Database - ) - ) + Write-Verbose -Message ('Creating file group: {0}' -f $FileGroupSpec.Name) + + # Convert the spec object to SMO FileGroup + $fileGroupObject = ConvertTo-SqlDscFileGroup -DatabaseObject $Database -FileGroupSpec $FileGroupSpec } + else + { + $fileGroupObject = [Microsoft.SqlServer.Management.Smo.FileGroup]::new($Database, $Name) + } + } + } + else + { + if ($AsSpec.IsPresent) + { + $fileGroupObject = [DatabaseFileGroupSpec]::new($Name) - $serverObject = $Database.Parent + if ($PSBoundParameters.ContainsKey('Files')) + { + $fileGroupObject.Files = $Files + } - $descriptionMessage = $script:localizedData.FileGroup_Create_ShouldProcessDescription -f $Name, $Database.Name, $serverObject.InstanceName - $confirmationMessage = $script:localizedData.FileGroup_Create_ShouldProcessConfirmation -f $Name - $captionMessage = $script:localizedData.FileGroup_Create_ShouldProcessCaption + if ($PSBoundParameters.ContainsKey('ReadOnly')) + { + $fileGroupObject.ReadOnly = $ReadOnly + } - if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + if ($PSBoundParameters.ContainsKey('IsDefault')) { - $fileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $Database, $Name + $fileGroupObject.IsDefault = $IsDefault } } else { - $fileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' + $fileGroupObject = [Microsoft.SqlServer.Management.Smo.FileGroup]::new() $fileGroupObject.Name = $Name } - - return $fileGroupObject } + + return $fileGroupObject } diff --git a/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 index 3515e82bd5..edbf2eefd1 100644 --- a/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 @@ -180,17 +180,15 @@ Describe 'New-SqlDscDatabase' -Tag @('Integration_SQL2017', 'Integration_SQL2019 } It 'Should create a database with custom file groups and data files' { - # Create PRIMARY filegroup with data file - $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' - $primaryFilePath = Join-Path -Path $script:dataDirectory -ChildPath ($script:testDatabaseWithFileGroups + '_Primary.mdf') - $null = New-SqlDscDataFile -FileGroup $primaryFileGroup -Name ($script:testDatabaseWithFileGroups + '_Primary') -FileName $primaryFilePath -Force + # Create PRIMARY filegroup specification with data file using -AsSpec parameters + $primaryFile = New-SqlDscDataFile -Name ($script:testDatabaseWithFileGroups + '_Primary') -FileName (Join-Path -Path $script:dataDirectory -ChildPath ($script:testDatabaseWithFileGroups + '_Primary.mdf')) -IsPrimaryFile $true -AsSpec + $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -AsSpec - # Create a secondary filegroup with data file - $secondaryFileGroup = New-SqlDscFileGroup -Name 'SecondaryFG' - $secondaryFilePath = Join-Path -Path $script:dataDirectory -ChildPath ($script:testDatabaseWithFileGroups + '_Secondary.ndf') - $null = New-SqlDscDataFile -FileGroup $secondaryFileGroup -Name ($script:testDatabaseWithFileGroups + '_Secondary') -FileName $secondaryFilePath -Force + # Create a secondary filegroup specification with data file using -AsSpec parameters + $secondaryFile = New-SqlDscDataFile -Name ($script:testDatabaseWithFileGroups + '_Secondary') -FileName (Join-Path -Path $script:dataDirectory -ChildPath ($script:testDatabaseWithFileGroups + '_Secondary.ndf')) -AsSpec + $secondaryFileGroup = New-SqlDscFileGroup -Name 'SecondaryFG' -Files @($secondaryFile) -AsSpec - # Create database with file groups + # Create database with file group specifications $result = New-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseWithFileGroups -FileGroup @($primaryFileGroup, $secondaryFileGroup) -Force -ErrorAction 'Stop' $result | Should -Not -BeNullOrEmpty diff --git a/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 b/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 new file mode 100644 index 0000000000..08bf552921 --- /dev/null +++ b/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 @@ -0,0 +1,257 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $script:moduleUnderTest = Import-Module -Name $script:dscModuleName -PassThru -Force -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'DatabaseFileGroupSpec' -Tag 'DatabaseFileGroupSpec' { + Context 'When instantiating the class' { + It 'Should not throw when instantiated with default constructor' { + InModuleScope -ScriptBlock { + { [DatabaseFileGroupSpec]::new() } | Should -Not -Throw + } + } + + It 'Should not throw when instantiated with Name only' { + InModuleScope -ScriptBlock { + { [DatabaseFileGroupSpec]::new('PRIMARY') } | Should -Not -Throw + } + } + + It 'Should not throw when instantiated with Name and Files array' { + InModuleScope -ScriptBlock { + $files = @( + [DatabaseFileSpec]::new('File1', 'C:\Data\File1.mdf') + ) + { [DatabaseFileGroupSpec]::new('PRIMARY', $files) } | Should -Not -Throw + } + } + } + + Context 'When setting properties using the parameterized constructor with Name only' { + It 'Should set Name property and initialize empty Files array' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileGroupSpec]::new('MyFileGroup') + + $instance.Name | Should -Be 'MyFileGroup' + $instance.Files | Should -BeNullOrEmpty + } + } + } + + Context 'When setting properties using the parameterized constructor with Name and Files' { + It 'Should set Name and Files properties' { + InModuleScope -ScriptBlock { + $files = @( + [DatabaseFileSpec]::new('File1', 'C:\Data\File1.mdf'), + [DatabaseFileSpec]::new('File2', 'C:\Data\File2.ndf') + ) + $instance = [DatabaseFileGroupSpec]::new('SecondaryFG', $files) + + $instance.Name | Should -Be 'SecondaryFG' + $instance.Files | Should -HaveCount 2 + $instance.Files[0].Name | Should -Be 'File1' + $instance.Files[1].Name | Should -Be 'File2' + } + } + } + + Context 'When setting properties individually' { + It 'Should allow setting all properties' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileGroupSpec]::new() + $instance.Name = 'TestFileGroup' + $instance.Files = @( + [DatabaseFileSpec]@{ Name = 'TestFile'; FileName = 'D:\Data\TestFile.ndf' } + ) + $instance.ReadOnly = $true + $instance.IsDefault = $true + + $instance.Name | Should -Be 'TestFileGroup' + $instance.Files | Should -HaveCount 1 + $instance.Files[0].Name | Should -Be 'TestFile' + $instance.ReadOnly | Should -BeTrue + $instance.IsDefault | Should -BeTrue + } + } + } + + Context 'When using default property values' { + It 'Should have null or false default values' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileGroupSpec]::new() + + $instance.Name | Should -BeNullOrEmpty + $instance.Files | Should -BeNullOrEmpty + $instance.ReadOnly | Should -BeFalse + $instance.IsDefault | Should -BeFalse + } + } + } + + Context 'When using hashtable syntax for instantiation' { + It 'Should create instance with properties from hashtable' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileGroupSpec]@{ + Name = 'DataFileGroup' + Files = @( + [DatabaseFileSpec]@{ + Name = 'DataFile1' + FileName = 'E:\Data\DataFile1.ndf' + Size = 100.0 + Growth = 10.0 + GrowthType = 'MB' + } + ) + ReadOnly = $false + IsDefault = $false + } + + $instance.Name | Should -Be 'DataFileGroup' + $instance.Files | Should -HaveCount 1 + $instance.Files[0].Name | Should -Be 'DataFile1' + $instance.Files[0].FileName | Should -Be 'E:\Data\DataFile1.ndf' + $instance.ReadOnly | Should -BeFalse + $instance.IsDefault | Should -BeFalse + } + } + } + + Context 'When creating PRIMARY file group' { + It 'Should set IsDefault to true for PRIMARY file group' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileGroupSpec]@{ + Name = 'PRIMARY' + Files = @( + [DatabaseFileSpec]@{ + Name = 'MyDB' + FileName = 'C:\Data\MyDB.mdf' + IsPrimaryFile = $true + } + ) + IsDefault = $true + } + + $instance.Name | Should -Be 'PRIMARY' + $instance.Files[0].IsPrimaryFile | Should -BeTrue + $instance.IsDefault | Should -BeTrue + } + } + } + + Context 'When creating read-only file group' { + It 'Should set ReadOnly property' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileGroupSpec]@{ + Name = 'ReadOnlyFG' + ReadOnly = $true + } + + $instance.Name | Should -Be 'ReadOnlyFG' + $instance.ReadOnly | Should -BeTrue + } + } + } + + Context 'When adding multiple files to a file group' { + It 'Should accept array of DatabaseFileSpec objects' { + InModuleScope -ScriptBlock { + $files = @( + [DatabaseFileSpec]@{ Name = 'File1'; FileName = 'C:\Data\File1.ndf'; Size = 50.0 } + [DatabaseFileSpec]@{ Name = 'File2'; FileName = 'C:\Data\File2.ndf'; Size = 100.0 } + [DatabaseFileSpec]@{ Name = 'File3'; FileName = 'D:\Data\File3.ndf'; Size = 150.0 } + ) + + $instance = [DatabaseFileGroupSpec]@{ + Name = 'MultiFileFG' + Files = $files + } + + $instance.Files | Should -HaveCount 3 + $instance.Files[0].Name | Should -Be 'File1' + $instance.Files[1].Name | Should -Be 'File2' + $instance.Files[2].Name | Should -Be 'File3' + $instance.Files[0].Size | Should -Be 50.0 + $instance.Files[1].Size | Should -Be 100.0 + $instance.Files[2].Size | Should -Be 150.0 + } + } + } + + Context 'When creating a file group without files' { + It 'Should allow empty Files array' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileGroupSpec]@{ + Name = 'EmptyFG' + } + + $instance.Name | Should -Be 'EmptyFG' + $instance.Files | Should -BeNullOrEmpty + } + } + } + + Context 'When Files property contains DatabaseFileSpec with all properties set' { + It 'Should preserve all DatabaseFileSpec properties' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileGroupSpec]@{ + Name = 'DetailedFG' + Files = @( + [DatabaseFileSpec]@{ + Name = 'DetailedFile' + FileName = 'F:\Data\DetailedFile.ndf' + Size = 200.0 + MaxSize = 2000.0 + Growth = 20.0 + GrowthType = 'Percent' + IsPrimaryFile = $false + } + ) + } + + $file = $instance.Files[0] + $file.Name | Should -Be 'DetailedFile' + $file.FileName | Should -Be 'F:\Data\DetailedFile.ndf' + $file.Size | Should -Be 200.0 + $file.MaxSize | Should -Be 2000.0 + $file.Growth | Should -Be 20.0 + $file.GrowthType | Should -Be 'Percent' + $file.IsPrimaryFile | Should -BeFalse + } + } + } +} diff --git a/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 b/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 new file mode 100644 index 0000000000..58e11e63f7 --- /dev/null +++ b/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 @@ -0,0 +1,173 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $script:moduleUnderTest = Import-Module -Name $script:dscModuleName -PassThru -Force -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'DatabaseFileSpec' -Tag 'DatabaseFileSpec' { + Context 'When instantiating the class' { + It 'Should not throw when instantiated with default constructor' { + InModuleScope -ScriptBlock { + { [DatabaseFileSpec]::new() } | Should -Not -Throw + } + } + + It 'Should not throw when instantiated with Name and FileName' { + InModuleScope -ScriptBlock { + { [DatabaseFileSpec]::new('TestFile', 'C:\Data\TestFile.mdf') } | Should -Not -Throw + } + } + } + + Context 'When setting properties using the parameterized constructor' { + It 'Should set Name and FileName properties' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileSpec]::new('MyFile', 'D:\SQLData\MyFile.mdf') + + $instance.Name | Should -Be 'MyFile' + $instance.FileName | Should -Be 'D:\SQLData\MyFile.mdf' + } + } + } + + Context 'When setting properties individually' { + It 'Should allow setting all properties' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileSpec]::new() + $instance.Name = 'DataFile1' + $instance.FileName = 'C:\Data\DataFile1.ndf' + $instance.Size = 100.0 + $instance.MaxSize = 1000.0 + $instance.Growth = 10.0 + $instance.GrowthType = 'MB' + $instance.IsPrimaryFile = $true + + $instance.Name | Should -Be 'DataFile1' + $instance.FileName | Should -Be 'C:\Data\DataFile1.ndf' + $instance.Size | Should -Be 100.0 + $instance.MaxSize | Should -Be 1000.0 + $instance.Growth | Should -Be 10.0 + $instance.GrowthType | Should -Be 'MB' + $instance.IsPrimaryFile | Should -BeTrue + } + } + } + + Context 'When using default property values' { + It 'Should have null or false default values' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileSpec]::new() + + $instance.Name | Should -BeNullOrEmpty + $instance.FileName | Should -BeNullOrEmpty + $instance.Size | Should -BeNullOrEmpty + $instance.MaxSize | Should -BeNullOrEmpty + $instance.Growth | Should -BeNullOrEmpty + $instance.GrowthType | Should -BeNullOrEmpty + $instance.IsPrimaryFile | Should -BeFalse + } + } + } + + Context 'When using hashtable syntax for instantiation' { + It 'Should create instance with properties from hashtable' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileSpec]@{ + Name = 'SecondaryFile' + FileName = 'E:\Data\SecondaryFile.ndf' + Size = 50.0 + MaxSize = 500.0 + Growth = 5.0 + GrowthType = 'Percent' + } + + $instance.Name | Should -Be 'SecondaryFile' + $instance.FileName | Should -Be 'E:\Data\SecondaryFile.ndf' + $instance.Size | Should -Be 50.0 + $instance.MaxSize | Should -Be 500.0 + $instance.Growth | Should -Be 5.0 + $instance.GrowthType | Should -Be 'Percent' + $instance.IsPrimaryFile | Should -BeFalse + } + } + } + + Context 'When specifying a primary file' { + It 'Should set IsPrimaryFile to true' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileSpec]@{ + Name = 'PrimaryFile' + FileName = 'C:\Data\MyDB.mdf' + IsPrimaryFile = $true + } + + $instance.Name | Should -Be 'PrimaryFile' + $instance.FileName | Should -Be 'C:\Data\MyDB.mdf' + $instance.IsPrimaryFile | Should -BeTrue + } + } + } + + Context 'When using different growth types' { + It 'Should accept MB as growth type' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileSpec]::new() + $instance.GrowthType = 'MB' + + $instance.GrowthType | Should -Be 'MB' + } + } + + It 'Should accept Percent as growth type' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileSpec]::new() + $instance.GrowthType = 'Percent' + + $instance.GrowthType | Should -Be 'Percent' + } + } + + It 'Should accept KB as growth type' { + InModuleScope -ScriptBlock { + $instance = [DatabaseFileSpec]::new() + $instance.GrowthType = 'KB' + + $instance.GrowthType | Should -Be 'KB' + } + } + } +} diff --git a/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 new file mode 100644 index 0000000000..c469a4af51 --- /dev/null +++ b/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 @@ -0,0 +1,189 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + # Loading mocked classes BEFORE importing the module (required for classes that reference SMO types) + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'ConvertTo-SqlDscDataFile' -Tag 'Public' { + Context 'When converting a DatabaseFileSpec to SMO DataFile' { + BeforeAll { + # Create mock FileGroup + $mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' + $mockFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force + } + + It 'Should convert a basic file spec with only required properties' { + InModuleScope -Parameters @{ + mockFileGroup = $mockFileGroup + } -ScriptBlock { + param ($mockFileGroup) + + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'TestFile' + $result.FileName | Should -Be 'C:\SQLData\TestFile.mdf' + } + } + + It 'Should convert a file spec with all optional properties set' { + InModuleScope -Parameters @{ + mockFileGroup = $mockFileGroup + } -ScriptBlock { + param ($mockFileGroup) + + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' ` + -Size 102400 -MaxSize 512000 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile $true -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestFile' + $result.FileName | Should -Be 'C:\SQLData\TestFile.mdf' + $result.Size | Should -Be 102400 + $result.MaxSize | Should -Be 512000 + $result.Growth | Should -Be 10240 + $result.GrowthType | Should -Be 'KB' + $result.IsPrimaryFile | Should -Be $true + } + } + + It 'Should convert a file spec with Size property' { + InModuleScope -Parameters @{ + mockFileGroup = $mockFileGroup + } -ScriptBlock { + param ($mockFileGroup) + + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -Size 204800 -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + + $result.Size | Should -Be 204800 + } + } + + It 'Should convert a file spec with MaxSize property' { + InModuleScope -Parameters @{ + mockFileGroup = $mockFileGroup + } -ScriptBlock { + param ($mockFileGroup) + + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -MaxSize 1024000 -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + + $result.MaxSize | Should -Be 1024000 + } + } + + It 'Should convert a file spec with Growth property' { + InModuleScope -Parameters @{ + mockFileGroup = $mockFileGroup + } -ScriptBlock { + param ($mockFileGroup) + + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -Growth 20480 -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + + $result.Growth | Should -Be 20480 + } + } + + It 'Should convert a file spec with GrowthType property set to Percent' { + InModuleScope -Parameters @{ + mockFileGroup = $mockFileGroup + } -ScriptBlock { + param ($mockFileGroup) + + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -GrowthType 'Percent' -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + + $result.GrowthType | Should -Be 'Percent' + } + } + + It 'Should convert a file spec with IsPrimaryFile property' { + InModuleScope -Parameters @{ + mockFileGroup = $mockFileGroup + } -ScriptBlock { + param ($mockFileGroup) + + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -IsPrimaryFile $true -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + + $result.IsPrimaryFile | Should -Be $true + } + } + } + + Context 'Parameter validation' { + BeforeAll { + $commandInfo = Get-Command -Name 'ConvertTo-SqlDscDataFile' + } + + It 'Should have FileGroupObject as a mandatory parameter' { + $parameterInfo = $commandInfo.Parameters['FileGroupObject'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have DataFileSpec as a mandatory parameter' { + $parameterInfo = $commandInfo.Parameters['DataFileSpec'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have OutputType set to Microsoft.SqlServer.Management.Smo.DataFile' { + $commandInfo.OutputType.Name | Should -Contain 'Microsoft.SqlServer.Management.Smo.DataFile' + } + } +} diff --git a/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 new file mode 100644 index 0000000000..e7d640a644 --- /dev/null +++ b/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 @@ -0,0 +1,207 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + # Loading mocked classes BEFORE importing the module (required for classes that reference SMO types) + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'ConvertTo-SqlDscFileGroup' -Tag 'Public' { + Context 'When converting a DatabaseFileGroupSpec to SMO FileGroup' { + BeforeAll { + # Create mock Database + $mockDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + } + + It 'Should convert a basic file group spec with only required properties' { + InModuleScope -Parameters @{ + mockDatabase = $mockDatabase + } -ScriptBlock { + param ($mockDatabase) + + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -AsSpec + + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'PRIMARY' + } + } + + It 'Should convert a file group spec with ReadOnly property' { + InModuleScope -Parameters @{ + mockDatabase = $mockDatabase + } -ScriptBlock { + param ($mockDatabase) + + $fileGroupSpec = New-SqlDscFileGroup -Name 'READONLY_FG' -ReadOnly $true -AsSpec + + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + + $result.Name | Should -Be 'READONLY_FG' + $result.ReadOnly | Should -Be $true + } + } + + It 'Should convert a file group spec with IsDefault property' { + InModuleScope -Parameters @{ + mockDatabase = $mockDatabase + } -ScriptBlock { + param ($mockDatabase) + + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -IsDefault $true -AsSpec + + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + + $result.Name | Should -Be 'PRIMARY' + $result.IsDefault | Should -Be $true + } + } + + It 'Should convert a file group spec with a single data file' { + InModuleScope -Parameters @{ + mockDatabase = $mockDatabase + } -ScriptBlock { + param ($mockDatabase) + + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($fileSpec) -AsSpec + + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + + $result.Name | Should -Be 'PRIMARY' + $result.Files.Count | Should -Be 1 + $result.Files[0].Name | Should -Be 'TestFile' + $result.Files[0].FileName | Should -Be 'C:\SQLData\TestFile.mdf' + } + } + + It 'Should convert a file group spec with multiple data files' { + InModuleScope -Parameters @{ + mockDatabase = $mockDatabase + } -ScriptBlock { + param ($mockDatabase) + + $fileSpec1 = New-SqlDscDataFile -Name 'TestFile1' -FileName 'C:\SQLData\TestFile1.ndf' -Size 102400 -AsSpec + $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\SQLData\TestFile2.ndf' -Size 204800 -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'SECONDARY' -Files @($fileSpec1, $fileSpec2) -AsSpec + + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + + $result.Name | Should -Be 'SECONDARY' + $result.Files.Count | Should -Be 2 + $result.Files[0].Name | Should -Be 'TestFile1' + $result.Files[0].Size | Should -Be 102400 + $result.Files[1].Name | Should -Be 'TestFile2' + $result.Files[1].Size | Should -Be 204800 + } + } + + It 'Should convert a file group spec with all properties and multiple files' { + InModuleScope -Parameters @{ + mockDatabase = $mockDatabase + } -ScriptBlock { + param ($mockDatabase) + + $primaryFile = New-SqlDscDataFile -Name 'PrimaryFile' -FileName 'C:\SQLData\Primary.mdf' ` + -Size 102400 -MaxSize 512000 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile $true -AsSpec + + $secondaryFile = New-SqlDscDataFile -Name 'SecondaryFile' -FileName 'C:\SQLData\Secondary.ndf' ` + -Size 204800 -MaxSize 1024000 -Growth 20480 -GrowthType 'KB' -AsSpec + + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile, $secondaryFile) ` + -ReadOnly $false -IsDefault $true -AsSpec + + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + + $result.Name | Should -Be 'PRIMARY' + $result.ReadOnly | Should -Be $false + $result.IsDefault | Should -Be $true + $result.Files.Count | Should -Be 2 + $result.Files[0].Name | Should -Be 'PrimaryFile' + $result.Files[0].IsPrimaryFile | Should -Be $true + $result.Files[1].Name | Should -Be 'SecondaryFile' + } + } + + It 'Should convert a file group spec without files (empty Files array)' { + InModuleScope -Parameters @{ + mockDatabase = $mockDatabase + } -ScriptBlock { + param ($mockDatabase) + + $fileGroupSpec = New-SqlDscFileGroup -Name 'EMPTY_FG' -AsSpec + + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + + $result.Name | Should -Be 'EMPTY_FG' + $result.Files.Count | Should -Be 0 + } + } + } + + Context 'Parameter validation' { + BeforeAll { + $commandInfo = Get-Command -Name 'ConvertTo-SqlDscFileGroup' + } + + It 'Should have DatabaseObject as a mandatory parameter' { + $parameterInfo = $commandInfo.Parameters['DatabaseObject'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have FileGroupSpec as a mandatory parameter' { + $parameterInfo = $commandInfo.Parameters['FileGroupSpec'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have OutputType set to Microsoft.SqlServer.Management.Smo.FileGroup' { + $commandInfo.OutputType.Name | Should -Contain 'Microsoft.SqlServer.Management.Smo.FileGroup' + } + } +} diff --git a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 index 6524e559fa..9da065fee2 100644 --- a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 @@ -160,8 +160,11 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { $commandInfo = Get-Command -Name 'New-SqlDscDataFile' } - It 'Should have one parameter set' { - $commandInfo.ParameterSets.Count | Should -Be 1 + It 'Should have three parameter sets (Standard, FromSpec, AsSpec)' { + $commandInfo.ParameterSets.Count | Should -Be 3 + $commandInfo.ParameterSets.Name | Should -Contain 'Standard' + $commandInfo.ParameterSets.Name | Should -Contain 'FromSpec' + $commandInfo.ParameterSets.Name | Should -Contain 'AsSpec' } It 'Should have FileGroup as a mandatory parameter' { diff --git a/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 b/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 index aa0a645fb2..43c45ceea0 100644 --- a/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 @@ -157,7 +157,7 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { It 'Should have the correct parameters in parameter set Database' -ForEach @( @{ ExpectedParameterSetName = 'Database' - ExpectedParameters = '-ServerObject -Name [-Collation ] [-CatalogCollation ] [-CompatibilityLevel ] [-RecoveryModel ] [-OwnerName ] [-FileGroup ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + ExpectedParameters = '-ServerObject -Name [-Collation ] [-CatalogCollation ] [-CompatibilityLevel ] [-RecoveryModel ] [-OwnerName ] [-FileGroup ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscDatabase').ParameterSets | @@ -174,7 +174,7 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { It 'Should have the correct parameters in parameter set Snapshot' -ForEach @( @{ ExpectedParameterSetName = 'Snapshot' - ExpectedParameters = '-ServerObject -Name -DatabaseSnapshotBaseName [-FileGroup ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + ExpectedParameters = '-ServerObject -Name -DatabaseSnapshotBaseName [-FileGroup ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscDatabase').ParameterSets | @@ -285,25 +285,107 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { } # Mock the helper commands used by New-SqlDscDatabase + Mock -CommandName 'New-SqlDscFileGroup' -ParameterFilter { $null -ne $FileGroupSpec } -MockWith { + $mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' + $mockFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $FileGroupSpec.Name -Force + return $mockFileGroup + } + Mock -CommandName 'Add-SqlDscFileGroup' } - It 'Should create a database snapshot with FileGroup successfully' { + It 'Should create a database snapshot with FileGroup spec successfully' { InModuleScope -Parameters @{ mockServerObject = $mockServerObject } -ScriptBlock { - $mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $null, 'PRIMARY' - $mockDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' -ArgumentList $mockFileGroup, 'TestSnapshot_Data', 'C:\Snapshots\TestSnapshot_Data.ss' - $mockFileGroup.Files.Add($mockDataFile) + # Create file spec using New-SqlDscDataFile with -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'TestSnapshot_Data' -FileName 'C:\Snapshots\TestSnapshot_Data.ss' -AsSpec - $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseSnapshotBaseName 'SourceDatabase' -FileGroup @($mockFileGroup) -Force + # Create filegroup spec using New-SqlDscFileGroup with -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($fileSpec) -AsSpec + + $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseSnapshotBaseName 'SourceDatabase' -FileGroup @($fileGroupSpec) -Force $result | Should -Not -BeNullOrEmpty $result.Name | Should -Be 'TestSnapshot' $result.DatabaseSnapshotBaseName | Should -Be 'SourceDatabase' } + Should -Invoke -CommandName 'New-SqlDscFileGroup' -Exactly -Times 1 -Scope It + Should -Invoke -CommandName 'Add-SqlDscFileGroup' -Exactly -Times 1 -Scope It + } + } + + Context 'When creating a database with custom file groups using spec objects' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'VersionMajor' -Value 15 -Force + $mockServerObject | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{} | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation + } -PassThru -Force + } -Force + + Mock -CommandName 'New-Object' -ParameterFilter { $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Database' } -MockWith { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $ArgumentList[1] -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'RecoveryModel' -Value $null -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + # Mock implementation + } -Force + return $mockDatabaseObject + } + + Mock -CommandName 'New-SqlDscFileGroup' -ParameterFilter { $null -ne $FileGroupSpec } -MockWith { + $mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' + $mockFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $FileGroupSpec.Name -Force + return $mockFileGroup + } + + Mock -CommandName 'Add-SqlDscFileGroup' + } + + It 'Should create a database with custom PRIMARY filegroup using -AsSpec parameters' { + InModuleScope -Parameters @{ + mockServerObject = $mockServerObject + } -ScriptBlock { + # Create file spec with parameters using -AsSpec + $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile $true -AsSpec + + # Create filegroup spec with parameters using -AsSpec + $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault $true -AsSpec + + $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestDB' -FileGroup @($primaryFileGroup) -Force + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestDB' + } + + Should -Invoke -CommandName 'New-SqlDscFileGroup' -Exactly -Times 1 -Scope It Should -Invoke -CommandName 'Add-SqlDscFileGroup' -Exactly -Times 1 -Scope It } + + It 'Should create a database with multiple filegroups using -AsSpec parameters' { + InModuleScope -Parameters @{ + mockServerObject = $mockServerObject + } -ScriptBlock { + # Create PRIMARY filegroup + $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -IsPrimaryFile $true -AsSpec + $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault $true -AsSpec + + # Create secondary filegroup + $secondaryFile = New-SqlDscDataFile -Name 'TestDB_Secondary' -FileName 'E:\SQLData\TestDB.ndf' -Size 204800 -AsSpec + $secondaryFileGroup = New-SqlDscFileGroup -Name 'SECONDARY' -Files @($secondaryFile) -AsSpec + + $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestDB' -FileGroup @($primaryFileGroup, $secondaryFileGroup) -Force + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestDB' + } + + Should -Invoke -CommandName 'New-SqlDscFileGroup' -Exactly -Times 2 -Scope It + Should -Invoke -CommandName 'Add-SqlDscFileGroup' -Exactly -Times 2 -Scope It + } } } diff --git a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 index 8f108f393a..a8c80ad432 100644 --- a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 @@ -89,18 +89,10 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { } } - It 'Should accept Database parameter from pipeline' { - InModuleScope -Parameters @{ - mockDatabaseObject = $mockDatabaseObject - } -ScriptBlock { - param ($mockDatabaseObject) - - $result = $mockDatabaseObject | New-SqlDscFileGroup -Name 'PipelineFileGroup' -Confirm:$false - - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'PipelineFileGroup' - $result.Parent | Should -Be $mockDatabaseObject - } + It 'Should not accept Database parameter from pipeline (removed to prevent file path reuse)' { + # Verify that the parameter doesn't have ValueFromPipeline attribute + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] + $parameterInfo.Attributes.ValueFromPipeline | Should -Not -Contain $true } It 'Should support Force parameter to bypass confirmation' { @@ -180,21 +172,22 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { $parameterInfo.Attributes.Mandatory | Should -Contain $true } - It 'Should have Database parameter accept pipeline input' { + It 'Should not have Database parameter accept pipeline input' { $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] - $parameterInfo.Attributes.ValueFromPipeline | Should -Contain $true + $parameterInfo.Attributes.ValueFromPipeline | Should -Not -Contain $true } - It 'Should have two parameter sets' { + It 'Should have three parameter sets (WithDatabase, WithDatabaseFromSpec, AsSpec)' { $command = Get-Command -Name 'New-SqlDscFileGroup' - $command.ParameterSets.Count | Should -Be 2 + $command.ParameterSets.Count | Should -Be 3 $command.ParameterSets.Name | Should -Contain 'WithDatabase' - $command.ParameterSets.Name | Should -Contain 'Standalone' + $command.ParameterSets.Name | Should -Contain 'WithDatabaseFromSpec' + $command.ParameterSets.Name | Should -Contain 'AsSpec' } - It 'Should have Standalone as the default parameter set' { + It 'Should have AsSpec as the default parameter set' { $command = Get-Command -Name 'New-SqlDscFileGroup' - $command.DefaultParameterSet | Should -Be 'Standalone' + $command.DefaultParameterSet | Should -Be 'AsSpec' } It 'Should support ShouldProcess' { diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index bd6f720221..40ab973809 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -996,6 +996,8 @@ public FileGroup(Database database, string name) public string Name { get; set; } public Database Parent { get; set; } public DataFileCollection Files { get; set; } + public bool ReadOnly { get; set; } + public bool IsDefault { get; set; } public void Create() { @@ -1057,6 +1059,11 @@ public DataFile(FileGroup fileGroup, string name, string fileName) public string Name { get; set; } public string FileName { get; set; } public FileGroup Parent { get; set; } + public double Size { get; set; } + public double MaxSize { get; set; } + public double Growth { get; set; } + public string GrowthType { get; set; } + public bool IsPrimaryFile { get; set; } public void Create() { From ff885545c99b6296db96d0f873312b624faf05e5 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 10:15:36 +0100 Subject: [PATCH 37/87] Add new commands and PowerShell classes for database file and file group specifications --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8485694b19..17c4c487dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -151,6 +151,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 their expected values, and `$false` otherwise. This command improves maintainability of SQL DSC resources and provides granular database configuration testing [issue #2306](https://github.com/dsccommunity/SqlServerDsc/issues/2306). +- Added public command `ConvertTo-SqlDscFileGroup` to convert `DatabaseFileGroupSpec` + objects to SMO FileGroup objects. +- Added public command `ConvertTo-SqlDscDataFile` to convert `DatabaseFileSpec` + objects to SMO DataFile objects. +- Added PowerShell class `DatabaseFileGroupSpec` for defining file group + specifications without requiring SMO context. +- Added PowerShell class `DatabaseFileSpec` for defining data file specifications + without requiring SMO context. ### Fixed From 71d9956a13b51957c2ec32da1437ae7c30455253 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 10:15:55 +0100 Subject: [PATCH 38/87] Add file organization guidelines for classes and enums --- .../instructions/dsc-community-style-guidelines.instructions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/instructions/dsc-community-style-guidelines.instructions.md b/.github/instructions/dsc-community-style-guidelines.instructions.md index f0d5748090..5bab164664 100644 --- a/.github/instructions/dsc-community-style-guidelines.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines.instructions.md @@ -19,6 +19,8 @@ applyTo: "**" ## File Organization - Public commands: `source/Public/{CommandName}.ps1` - Private functions: `source/Private/{FunctionName}.ps1` +- Classes: `source/Classes/{DependencyGroupNumber}.{ClassName}.ps1` +- Enums: `source/Enum/{DependencyGroupNumber}.{EnumName}.ps1` - Unit tests: `tests/Unit/{Classes|Public|Private}/{Name}.Tests.ps1` - Integration tests: `tests/Integration/Commands/{CommandName}.Integration.Tests.ps1` From 0fcf82b4e826d8e24af6b7221c07bdc40b2002d1 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 10:30:13 +0100 Subject: [PATCH 39/87] Add suppression message for ScriptAnalyzer rule in New-SqlDscFileGroup function --- source/Public/New-SqlDscFileGroup.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index 2e54e70123..04d98ebfdd 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -80,6 +80,7 @@ #> function New-SqlDscFileGroup { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [CmdletBinding(DefaultParameterSetName = 'AsSpec', SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup])] [OutputType([DatabaseFileGroupSpec])] From 720aea34b298192ca4c241971701fd6c59b9e07a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 10:46:40 +0100 Subject: [PATCH 40/87] Add new commands and integration tests for converting DatabaseFileSpec and DatabaseFileGroupSpec --- CHANGELOG.md | 16 +- azure-pipelines.yml | 2 + ...ertTo-SqlDscDataFile.Integration.Tests.ps1 | 163 ++++++++++++++++ ...rtTo-SqlDscFileGroup.Integration.Tests.ps1 | 182 ++++++++++++++++++ 4 files changed, 355 insertions(+), 8 deletions(-) create mode 100644 tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 create mode 100644 tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index a18efafd6c..40d184ab80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added public command `Add-SqlDscFileGroup` to add one or more FileGroup objects to a Database. This command provides a clean way to associate FileGroup objects with a Database after they have been created. +- Added public command `ConvertTo-SqlDscDataFile` to convert `DatabaseFileSpec` + objects to SMO DataFile objects. +- Added public command `ConvertTo-SqlDscFileGroup` to convert `DatabaseFileGroupSpec` + objects to SMO FileGroup objects. +- Added class `DatabaseFileSpec` to define data file specifications without requiring + a database or SMO context. +- Added class `DatabaseFileGroupSpec` to define file group specifications with + associated data files without requiring a database or SMO context. - `New-SqlDscDatabase` - Added `FileGroup` and `DataFile` parameters to allow specifying custom file locations and structure. These parameters apply to both regular databases and @@ -151,14 +159,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 their expected values, and `$false` otherwise. This command improves maintainability of SQL DSC resources and provides granular database configuration testing [issue #2306](https://github.com/dsccommunity/SqlServerDsc/issues/2306). -- Added public command `ConvertTo-SqlDscFileGroup` to convert `DatabaseFileGroupSpec` - objects to SMO FileGroup objects. -- Added public command `ConvertTo-SqlDscDataFile` to convert `DatabaseFileSpec` - objects to SMO DataFile objects. -- Added PowerShell class `DatabaseFileGroupSpec` for defining file group - specifications without requiring SMO context. -- Added PowerShell class `DatabaseFileSpec` for defining data file specifications - without requiring SMO context. ### Fixed diff --git a/azure-pipelines.yml b/azure-pipelines.yml index daf94fe19e..f782a2e3dc 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -296,6 +296,8 @@ stages: 'tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1' 'tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1' + 'tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1' + 'tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscSetupLog.Integration.Tests.ps1' 'tests/Integration/Commands/Connect-SqlDscDatabaseEngine.Integration.Tests.ps1' 'tests/Integration/Commands/Disconnect-SqlDscDatabaseEngine.Integration.Tests.ps1' diff --git a/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 new file mode 100644 index 0000000000..236ef5b8b0 --- /dev/null +++ b/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 @@ -0,0 +1,163 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Import the SMO module to ensure real SMO types are available + Import-SqlDscPreferredModule +} + +Describe 'ConvertTo-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } + + Context 'When converting a DatabaseFileSpec to a DataFile with real SMO types' { + BeforeAll { + $script:testDatabaseName = 'TestDB_{0}' -f (Get-Random) + + # Create a test database for the file group context + $script:testDatabase = New-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -Confirm:$false + } + + AfterAll { + # Clean up the test database + if ($script:testDatabase) + { + Remove-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -Confirm:$false + } + } + + BeforeEach { + $script:mockFileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'TestFileGroup' -Confirm:$false + } + + It 'Should convert a minimal DatabaseFileSpec to a DataFile object' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.DataFile] + $result.Name | Should -Be 'TestFile' + $result.FileName | Should -Be 'C:\Data\TestFile.ndf' + } + + It 'Should convert a DatabaseFileSpec with Size to a DataFile object' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -Size 100 -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + + $result | Should -Not -BeNullOrEmpty + $result.Size | Should -Be 100 + } + + It 'Should convert a DatabaseFileSpec with MaxSize to a DataFile object' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -MaxSize 1000 -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + + $result | Should -Not -BeNullOrEmpty + $result.MaxSize | Should -Be 1000 + } + + It 'Should convert a DatabaseFileSpec with Growth to a DataFile object' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -Growth 10 -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + + $result | Should -Not -BeNullOrEmpty + $result.Growth | Should -Be 10 + } + + It 'Should convert a DatabaseFileSpec with GrowthType to a DataFile object' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -GrowthType 'Percent' -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + + $result | Should -Not -BeNullOrEmpty + $result.GrowthType | Should -Be 'Percent' + } + + It 'Should convert a DatabaseFileSpec with IsPrimaryFile to a DataFile object' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.mdf' -IsPrimaryFile $true -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + + $result | Should -Not -BeNullOrEmpty + $result.IsPrimaryFile | Should -Be $true + } + + It 'Should convert a DatabaseFileSpec with all optional properties to a DataFile object' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -Size 100 -MaxSize 1000 -Growth 10 -GrowthType 'Percent' -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestFile' + $result.FileName | Should -Be 'C:\Data\TestFile.ndf' + $result.Size | Should -Be 100 + $result.MaxSize | Should -Be 1000 + $result.Growth | Should -Be 10 + $result.GrowthType | Should -Be 'Percent' + } + + It 'Should accept DatabaseFileSpec from pipeline' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec + + $result = $fileSpec | ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.DataFile] + $result.Name | Should -Be 'TestFile' + } + + It 'Should convert multiple DatabaseFileSpecs from pipeline' { + $fileSpec1 = New-SqlDscDataFile -Name 'TestFile1' -FileName 'C:\Data\TestFile1.ndf' -AsSpec + $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\Data\TestFile2.ndf' -AsSpec + + $result = @($fileSpec1, $fileSpec2) | ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup + + $result | Should -HaveCount 2 + $result[0].Name | Should -Be 'TestFile1' + $result[1].Name | Should -Be 'TestFile2' + } + } +} diff --git a/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 new file mode 100644 index 0000000000..ecd50f834a --- /dev/null +++ b/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 @@ -0,0 +1,182 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Import the SMO module to ensure real SMO types are available + Import-SqlDscPreferredModule +} + +Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } + + Context 'When converting a DatabaseFileGroupSpec to a FileGroup with real SMO types' { + BeforeAll { + $script:testDatabaseName = 'TestDB_{0}' -f (Get-Random) + + # Create a test database for the conversion context + $script:testDatabase = New-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -Confirm:$false + } + + AfterAll { + # Clean up the test database + if ($script:testDatabase) + { + Remove-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -Confirm:$false + } + } + + It 'Should convert a minimal DatabaseFileGroupSpec to a FileGroup object' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -AsSpec + + $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.FileGroup] + $result.Name | Should -Be 'TestFileGroup' + $result.Files.Count | Should -Be 1 + $result.Files[0].Name | Should -Be 'TestFile' + } + + It 'Should convert a DatabaseFileGroupSpec with multiple files to a FileGroup object' { + $fileSpec1 = New-SqlDscDataFile -Name 'TestFile1' -FileName 'C:\Data\TestFile1.ndf' -AsSpec + $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\Data\TestFile2.ndf' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec1, $fileSpec2) -AsSpec + + $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + + $result | Should -Not -BeNullOrEmpty + $result.Files.Count | Should -Be 2 + $result.Files[0].Name | Should -Be 'TestFile1' + $result.Files[1].Name | Should -Be 'TestFile2' + } + + It 'Should convert a DatabaseFileGroupSpec with ReadOnly to a FileGroup object' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -ReadOnly $true -AsSpec + + $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + + $result | Should -Not -BeNullOrEmpty + $result.ReadOnly | Should -Be $true + } + + It 'Should convert a DatabaseFileGroupSpec with IsDefault to a FileGroup object' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -IsDefault $true -AsSpec + + $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + + $result | Should -Not -BeNullOrEmpty + $result.IsDefault | Should -Be $true + } + + It 'Should convert a DatabaseFileGroupSpec with all optional properties to a FileGroup object' { + $fileSpec1 = New-SqlDscDataFile -Name 'TestFile1' -FileName 'C:\Data\TestFile1.ndf' -Size 100 -MaxSize 1000 -AsSpec + $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\Data\TestFile2.ndf' -Growth 10 -GrowthType 'Percent' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec1, $fileSpec2) -ReadOnly $false -IsDefault $false -AsSpec + + $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestFileGroup' + $result.Files.Count | Should -Be 2 + $result.Files[0].Size | Should -Be 100 + $result.Files[0].MaxSize | Should -Be 1000 + $result.Files[1].Growth | Should -Be 10 + $result.Files[1].GrowthType | Should -Be 'Percent' + $result.ReadOnly | Should -Be $false + $result.IsDefault | Should -Be $false + } + + It 'Should accept DatabaseFileGroupSpec from pipeline' { + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -AsSpec + + $result = $fileGroupSpec | ConvertTo-SqlDscFileGroup -Database $script:testDatabase + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.FileGroup] + $result.Name | Should -Be 'TestFileGroup' + } + + It 'Should convert multiple DatabaseFileGroupSpecs from pipeline' { + $fileSpec1 = New-SqlDscDataFile -Name 'TestFile1' -FileName 'C:\Data\TestFile1.ndf' -AsSpec + $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\Data\TestFile2.ndf' -AsSpec + $fileGroupSpec1 = New-SqlDscFileGroup -Name 'FileGroup1' -Files @($fileSpec1) -AsSpec + $fileGroupSpec2 = New-SqlDscFileGroup -Name 'FileGroup2' -Files @($fileSpec2) -AsSpec + + $result = @($fileGroupSpec1, $fileGroupSpec2) | ConvertTo-SqlDscFileGroup -Database $script:testDatabase + + $result | Should -HaveCount 2 + $result[0].Name | Should -Be 'FileGroup1' + $result[1].Name | Should -Be 'FileGroup2' + } + + It 'Should preserve file properties when converting FileGroup with complex file configurations' { + $primaryFile = New-SqlDscDataFile -Name 'PrimaryFile' -FileName 'C:\Data\Primary.mdf' -IsPrimaryFile $true -Size 200 -MaxSize 2000 -Growth 20 -GrowthType 'KB' -AsSpec + $secondaryFile = New-SqlDscDataFile -Name 'SecondaryFile' -FileName 'C:\Data\Secondary.ndf' -Size 100 -MaxSize 1000 -Growth 10 -GrowthType 'Percent' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'ComplexFileGroup' -Files @($primaryFile, $secondaryFile) -AsSpec + + $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + + $result | Should -Not -BeNullOrEmpty + $result.Files.Count | Should -Be 2 + + # Verify primary file properties + $result.Files[0].Name | Should -Be 'PrimaryFile' + $result.Files[0].IsPrimaryFile | Should -Be $true + $result.Files[0].Size | Should -Be 200 + $result.Files[0].MaxSize | Should -Be 2000 + $result.Files[0].Growth | Should -Be 20 + $result.Files[0].GrowthType | Should -Be 'KB' + + # Verify secondary file properties + $result.Files[1].Name | Should -Be 'SecondaryFile' + $result.Files[1].Size | Should -Be 100 + $result.Files[1].MaxSize | Should -Be 1000 + $result.Files[1].Growth | Should -Be 10 + $result.Files[1].GrowthType | Should -Be 'Percent' + } + } +} From 5eace92f8dc85a7ab1dc2b489d41e1dc1f7d5fb8 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 11:00:32 +0100 Subject: [PATCH 41/87] Add parameters for Size, MaxSize, Growth, GrowthType, and IsPrimaryFile to New-SqlDscDataFile function --- source/Public/New-SqlDscDataFile.ps1 | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index c2726f3893..523e68125c 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -30,6 +30,31 @@ or passed directly to New-SqlDscDatabase to define data files before the database is created. + .PARAMETER Size + Specifies the initial size of the data file in kilobytes. Only valid when + used with the -AsSpec parameter to create a DatabaseFileSpec object. + + .PARAMETER MaxSize + Specifies the maximum size to which the data file can grow, in kilobytes. + Only valid when used with the -AsSpec parameter to create a DatabaseFileSpec + object. + + .PARAMETER Growth + Specifies the amount by which the data file grows when it requires more space. + The value is interpreted according to the GrowthType parameter (kilobytes or + percentage). Only valid when used with the -AsSpec parameter to create a + DatabaseFileSpec object. + + .PARAMETER GrowthType + Specifies the type of growth for the data file. Valid values are 'KB', 'MB', + or 'Percent'. Only valid when used with the -AsSpec parameter to create a + DatabaseFileSpec object. + + .PARAMETER IsPrimaryFile + Specifies whether this data file is the primary file in the PRIMARY filegroup. + Only valid when used with the -AsSpec parameter to create a DatabaseFileSpec + object. + .PARAMETER PassThru Returns the DataFile object that was created and added to the FileGroup. From 3b361a8eb913b84c7d8c87f56b7d9769e69c78ed Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 11:00:44 +0100 Subject: [PATCH 42/87] Fix formatting in ConvertTo-SqlDscFileGroup integration tests --- .../Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 index ecd50f834a..aab14e7296 100644 --- a/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 @@ -162,7 +162,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ $result | Should -Not -BeNullOrEmpty $result.Files.Count | Should -Be 2 - + # Verify primary file properties $result.Files[0].Name | Should -Be 'PrimaryFile' $result.Files[0].IsPrimaryFile | Should -Be $true @@ -170,7 +170,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ $result.Files[0].MaxSize | Should -Be 2000 $result.Files[0].Growth | Should -Be 20 $result.Files[0].GrowthType | Should -Be 'KB' - + # Verify secondary file properties $result.Files[1].Name | Should -Be 'SecondaryFile' $result.Files[1].Size | Should -Be 100 From 062bdfffd2652b761956bfb78e9033387d49936c Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 12:17:59 +0100 Subject: [PATCH 43/87] Remove pipeline acceptance test for Database parameter in New-SqlDscFileGroup integration tests --- .../Commands/New-SqlDscFileGroup.Integration.Tests.ps1 | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 index bf97617690..7b199704bb 100644 --- a/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscFileGroup.Integration.Tests.ps1 @@ -86,15 +86,6 @@ Describe 'New-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 $result.Parent | Should -Be $script:mockDatabase } - It 'Should accept Database parameter from pipeline' { - $result = $script:mockDatabase | New-SqlDscFileGroup -Name 'PipelineFileGroup' -Confirm:$false - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' - $result.Name | Should -Be 'PipelineFileGroup' - $result.Parent | Should -Be $script:mockDatabase - } - It 'Should support Force parameter to bypass confirmation' { $result = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'ForcedFileGroup' -Force From 3dbc133a822aeae6d08b505b4a68553addd2c6d3 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 12:20:38 +0100 Subject: [PATCH 44/87] Refactor ConvertTo-SqlDscDataFile and ConvertTo-SqlDscFileGroup integration tests to use updated parameter names --- ...ertTo-SqlDscDataFile.Integration.Tests.ps1 | 34 ++++-------------- ...rtTo-SqlDscFileGroup.Integration.Tests.ps1 | 36 ++++--------------- 2 files changed, 13 insertions(+), 57 deletions(-) diff --git a/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 index 236ef5b8b0..85e2f4deac 100644 --- a/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 +++ b/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 @@ -72,7 +72,7 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_S It 'Should convert a minimal DatabaseFileSpec to a DataFile object' { $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $script:mockFileGroup -DataFileSpec $fileSpec $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.DataFile] @@ -83,7 +83,7 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_S It 'Should convert a DatabaseFileSpec with Size to a DataFile object' { $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -Size 100 -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $script:mockFileGroup -DataFileSpec $fileSpec $result | Should -Not -BeNullOrEmpty $result.Size | Should -Be 100 @@ -92,7 +92,7 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_S It 'Should convert a DatabaseFileSpec with MaxSize to a DataFile object' { $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -MaxSize 1000 -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $script:mockFileGroup -DataFileSpec $fileSpec $result | Should -Not -BeNullOrEmpty $result.MaxSize | Should -Be 1000 @@ -101,7 +101,7 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_S It 'Should convert a DatabaseFileSpec with Growth to a DataFile object' { $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -Growth 10 -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $script:mockFileGroup -DataFileSpec $fileSpec $result | Should -Not -BeNullOrEmpty $result.Growth | Should -Be 10 @@ -110,7 +110,7 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_S It 'Should convert a DatabaseFileSpec with GrowthType to a DataFile object' { $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -GrowthType 'Percent' -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $script:mockFileGroup -DataFileSpec $fileSpec $result | Should -Not -BeNullOrEmpty $result.GrowthType | Should -Be 'Percent' @@ -119,7 +119,7 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_S It 'Should convert a DatabaseFileSpec with IsPrimaryFile to a DataFile object' { $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.mdf' -IsPrimaryFile $true -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $script:mockFileGroup -DataFileSpec $fileSpec $result | Should -Not -BeNullOrEmpty $result.IsPrimaryFile | Should -Be $true @@ -128,7 +128,7 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_S It 'Should convert a DatabaseFileSpec with all optional properties to a DataFile object' { $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -Size 100 -MaxSize 1000 -Growth 10 -GrowthType 'Percent' -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup -DatabaseFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $script:mockFileGroup -DataFileSpec $fileSpec $result | Should -Not -BeNullOrEmpty $result.Name | Should -Be 'TestFile' @@ -139,25 +139,5 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_S $result.GrowthType | Should -Be 'Percent' } - It 'Should accept DatabaseFileSpec from pipeline' { - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec - - $result = $fileSpec | ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.DataFile] - $result.Name | Should -Be 'TestFile' - } - - It 'Should convert multiple DatabaseFileSpecs from pipeline' { - $fileSpec1 = New-SqlDscDataFile -Name 'TestFile1' -FileName 'C:\Data\TestFile1.ndf' -AsSpec - $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\Data\TestFile2.ndf' -AsSpec - - $result = @($fileSpec1, $fileSpec2) | ConvertTo-SqlDscDataFile -FileGroup $script:mockFileGroup - - $result | Should -HaveCount 2 - $result[0].Name | Should -Be 'TestFile1' - $result[1].Name | Should -Be 'TestFile2' - } } } diff --git a/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 index aab14e7296..89b3439f59 100644 --- a/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 @@ -69,7 +69,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -AsSpec - $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $script:testDatabase -FileGroupSpec $fileGroupSpec $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.FileGroup] @@ -83,7 +83,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\Data\TestFile2.ndf' -AsSpec $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec1, $fileSpec2) -AsSpec - $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $script:testDatabase -FileGroupSpec $fileGroupSpec $result | Should -Not -BeNullOrEmpty $result.Files.Count | Should -Be 2 @@ -95,7 +95,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -ReadOnly $true -AsSpec - $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $script:testDatabase -FileGroupSpec $fileGroupSpec $result | Should -Not -BeNullOrEmpty $result.ReadOnly | Should -Be $true @@ -105,7 +105,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -IsDefault $true -AsSpec - $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $script:testDatabase -FileGroupSpec $fileGroupSpec $result | Should -Not -BeNullOrEmpty $result.IsDefault | Should -Be $true @@ -116,7 +116,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\Data\TestFile2.ndf' -Growth 10 -GrowthType 'Percent' -AsSpec $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec1, $fileSpec2) -ReadOnly $false -IsDefault $false -AsSpec - $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $script:testDatabase -FileGroupSpec $fileGroupSpec $result | Should -Not -BeNullOrEmpty $result.Name | Should -Be 'TestFileGroup' @@ -129,36 +129,12 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ $result.IsDefault | Should -Be $false } - It 'Should accept DatabaseFileGroupSpec from pipeline' { - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -AsSpec - - $result = $fileGroupSpec | ConvertTo-SqlDscFileGroup -Database $script:testDatabase - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.FileGroup] - $result.Name | Should -Be 'TestFileGroup' - } - - It 'Should convert multiple DatabaseFileGroupSpecs from pipeline' { - $fileSpec1 = New-SqlDscDataFile -Name 'TestFile1' -FileName 'C:\Data\TestFile1.ndf' -AsSpec - $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\Data\TestFile2.ndf' -AsSpec - $fileGroupSpec1 = New-SqlDscFileGroup -Name 'FileGroup1' -Files @($fileSpec1) -AsSpec - $fileGroupSpec2 = New-SqlDscFileGroup -Name 'FileGroup2' -Files @($fileSpec2) -AsSpec - - $result = @($fileGroupSpec1, $fileGroupSpec2) | ConvertTo-SqlDscFileGroup -Database $script:testDatabase - - $result | Should -HaveCount 2 - $result[0].Name | Should -Be 'FileGroup1' - $result[1].Name | Should -Be 'FileGroup2' - } - It 'Should preserve file properties when converting FileGroup with complex file configurations' { $primaryFile = New-SqlDscDataFile -Name 'PrimaryFile' -FileName 'C:\Data\Primary.mdf' -IsPrimaryFile $true -Size 200 -MaxSize 2000 -Growth 20 -GrowthType 'KB' -AsSpec $secondaryFile = New-SqlDscDataFile -Name 'SecondaryFile' -FileName 'C:\Data\Secondary.ndf' -Size 100 -MaxSize 1000 -Growth 10 -GrowthType 'Percent' -AsSpec $fileGroupSpec = New-SqlDscFileGroup -Name 'ComplexFileGroup' -Files @($primaryFile, $secondaryFile) -AsSpec - $result = ConvertTo-SqlDscFileGroup -Database $script:testDatabase -DatabaseFileGroupSpec $fileGroupSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $script:testDatabase -FileGroupSpec $fileGroupSpec $result | Should -Not -BeNullOrEmpty $result.Files.Count | Should -Be 2 From 9ccc5dfbe5c99552efe1d0070e9d483b15aaeda3 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 14:10:01 +0100 Subject: [PATCH 45/87] Update parameter descriptions and usage for IsPrimaryFile in New-SqlDscDataFile and related tests --- source/Classes/002.DatabaseFileSpec.ps1 | 2 +- source/Public/New-SqlDscDataFile.ps1 | 32 +++++-- source/Public/New-SqlDscDatabase.ps1 | 2 +- source/Public/New-SqlDscFileGroup.ps1 | 14 +-- source/en-US/SqlServerDsc.strings.psd1 | 1 + ...ertTo-SqlDscDataFile.Integration.Tests.ps1 | 7 +- ...rtTo-SqlDscFileGroup.Integration.Tests.ps1 | 14 ++- .../New-SqlDscDatabase.Integration.Tests.ps1 | 2 +- .../Public/ConvertTo-SqlDscDataFile.Tests.ps1 | 4 +- .../ConvertTo-SqlDscFileGroup.Tests.ps1 | 9 +- .../Unit/Public/New-SqlDscDataFile.Tests.ps1 | 96 +++++++++++++++++++ .../Unit/Public/New-SqlDscDatabase.Tests.ps1 | 4 +- 12 files changed, 151 insertions(+), 36 deletions(-) diff --git a/source/Classes/002.DatabaseFileSpec.ps1 b/source/Classes/002.DatabaseFileSpec.ps1 index c8437922a7..6f024a65a2 100644 --- a/source/Classes/002.DatabaseFileSpec.ps1 +++ b/source/Classes/002.DatabaseFileSpec.ps1 @@ -33,7 +33,7 @@ If not specified, defaults to KB. .PARAMETER IsPrimaryFile - Specifies whether this file is the primary file in the PRIMARY file group. + Specifies that this file is the primary file in the PRIMARY file group. Only one file in the PRIMARY file group should be marked as the primary file. This property is typically used for the first file in the PRIMARY file group. diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index 523e68125c..029fd10d53 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -51,7 +51,7 @@ DatabaseFileSpec object. .PARAMETER IsPrimaryFile - Specifies whether this data file is the primary file in the PRIMARY filegroup. + Specifies that this data file is the primary file in the PRIMARY filegroup. Only valid when used with the -AsSpec parameter to create a DatabaseFileSpec object. @@ -94,7 +94,7 @@ or passed to New-SqlDscDatabase. .EXAMPLE - $dataFileSpec = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -Size 102400 -MaxSize 5242880 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile $true -AsSpec + $dataFileSpec = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -Size 102400 -MaxSize 5242880 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec Creates a DatabaseFileSpec object with all properties set directly via parameters. @@ -152,7 +152,7 @@ function New-SqlDscDataFile $GrowthType, [Parameter(ParameterSetName = 'AsSpec')] - [System.Boolean] + [System.Management.Automation.SwitchParameter] $IsPrimaryFile, [Parameter(ParameterSetName = 'Standard')] @@ -195,14 +195,32 @@ function New-SqlDscDataFile $fileSpec.GrowthType = $GrowthType } - if ($PSBoundParameters.ContainsKey('IsPrimaryFile')) + if ($IsPrimaryFile.IsPresent) { - $fileSpec.IsPrimaryFile = $IsPrimaryFile + $fileSpec.IsPrimaryFile = $true } return $fileSpec } + # Validate that primary files can only be in the PRIMARY filegroup + if ($PSCmdlet.ParameterSetName -in @('FromSpec')) + { + $isPrimary = $null -ne $DataFileSpec -and $DataFileSpec.IsPrimaryFile + + if ($isPrimary -and $FileGroup.Name -ne 'PRIMARY') + { + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + ($script:localizedData.DataFile_PrimaryFileMustBeInPrimaryFileGroup), + 'NSDDF0003', + [System.Management.Automation.ErrorCategory]::InvalidArgument, + $FileGroup + ) + ) + } + } + # Determine the data file name based on parameter set $dataFileName = if ($PSCmdlet.ParameterSetName -eq 'FromSpec') { @@ -223,8 +241,8 @@ function New-SqlDscDataFile { Write-Verbose -Message (' Creating data file: {0}' -f $DataFileSpec.Name) - # Use the spec object's method to create the SMO DataFile - $dataFileObject = $DataFileSpec.ToSmoDataFile($FileGroup) + # Convert the spec object to SMO DataFile + $dataFileObject = ConvertTo-SqlDscDataFile -FileGroupObject $FileGroup -DataFileSpec $DataFileSpec } else { diff --git a/source/Public/New-SqlDscDatabase.ps1 b/source/Public/New-SqlDscDatabase.ps1 index f2af623c7a..002c2ee585 100644 --- a/source/Public/New-SqlDscDatabase.ps1 +++ b/source/Public/New-SqlDscDatabase.ps1 @@ -84,7 +84,7 @@ .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - $primaryFile = New-SqlDscDataFile -Name 'MyDatabase_Primary' -FileName 'D:\SQLData\MyDatabase.mdf' -Size 102400 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile $true -AsSpec + $primaryFile = New-SqlDscDataFile -Name 'MyDatabase_Primary' -FileName 'D:\SQLData\MyDatabase.mdf' -Size 102400 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault $true -AsSpec $secondaryFile = New-SqlDscDataFile -Name 'MyDatabase_Secondary' -FileName 'E:\SQLData\MyDatabase.ndf' -Size 204800 -AsSpec diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index 04d98ebfdd..cb3a2eb01d 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -70,7 +70,7 @@ Creates a DatabaseFileGroupSpec object that can be passed to New-SqlDscDatabase. .EXAMPLE - $primaryFile = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -Size 102400 -IsPrimaryFile $true -AsSpec + $primaryFile = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -Size 102400 -IsPrimaryFile -AsSpec $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault $true -AsSpec Creates a DatabaseFileGroupSpec object with files and properties set directly via parameters. @@ -110,11 +110,11 @@ function New-SqlDscFileGroup $Files, [Parameter(ParameterSetName = 'AsSpec')] - [System.Boolean] + [System.Management.Automation.SwitchParameter] $ReadOnly, [Parameter(ParameterSetName = 'AsSpec')] - [System.Boolean] + [System.Management.Automation.SwitchParameter] $IsDefault, [Parameter(ParameterSetName = 'AsSpec')] @@ -194,14 +194,14 @@ function New-SqlDscFileGroup $fileGroupObject.Files = $Files } - if ($PSBoundParameters.ContainsKey('ReadOnly')) + if ($ReadOnly.IsPresent) { - $fileGroupObject.ReadOnly = $ReadOnly + $fileGroupObject.ReadOnly = $true } - if ($PSBoundParameters.ContainsKey('IsDefault')) + if ($IsDefault.IsPresent) { - $fileGroupObject.IsDefault = $IsDefault + $fileGroupObject.IsDefault = $true } } else diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 9ed1d16ef8..a55b6ca49e 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -381,6 +381,7 @@ ConvertFrom-StringData @' DataFile_Create_ShouldProcessConfirmation = Are you sure you want to create the data file '{0}'? (NSDDF0002) # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. DataFile_Create_ShouldProcessCaption = Create data file for filegroup + DataFile_PrimaryFileMustBeInPrimaryFileGroup = The primary file must reside in the PRIMARY filegroup. (NSDDF0003) ## Set-SqlDscDatabaseProperty Database_Set = Setting properties of database '{0}' on instance '{1}'. (SSDDP0001) diff --git a/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 index 85e2f4deac..806959bef2 100644 --- a/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 +++ b/tests/Integration/Commands/ConvertTo-SqlDscDataFile.Integration.Tests.ps1 @@ -117,9 +117,12 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_S } It 'Should convert a DatabaseFileSpec with IsPrimaryFile to a DataFile object' { - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.mdf' -IsPrimaryFile $true -AsSpec + # IsPrimaryFile can only be set for files in the PRIMARY filegroup + $primaryFileGroup = $script:testDatabase.FileGroups['PRIMARY'] - $result = ConvertTo-SqlDscDataFile -FileGroupObject $script:mockFileGroup -DataFileSpec $fileSpec + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.mdf' -IsPrimaryFile -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroupObject $primaryFileGroup -DataFileSpec $fileSpec $result | Should -Not -BeNullOrEmpty $result.IsPrimaryFile | Should -Be $true diff --git a/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 index 89b3439f59..a2eb67958a 100644 --- a/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/ConvertTo-SqlDscFileGroup.Integration.Tests.ps1 @@ -93,7 +93,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ It 'Should convert a DatabaseFileGroupSpec with ReadOnly to a FileGroup object' { $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -ReadOnly $true -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -ReadOnly -AsSpec $result = ConvertTo-SqlDscFileGroup -DatabaseObject $script:testDatabase -FileGroupSpec $fileGroupSpec @@ -103,7 +103,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ It 'Should convert a DatabaseFileGroupSpec with IsDefault to a FileGroup object' { $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\Data\TestFile.ndf' -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -IsDefault $true -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec) -IsDefault -AsSpec $result = ConvertTo-SqlDscFileGroup -DatabaseObject $script:testDatabase -FileGroupSpec $fileGroupSpec @@ -111,10 +111,10 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ $result.IsDefault | Should -Be $true } - It 'Should convert a DatabaseFileGroupSpec with all optional properties to a FileGroup object' { + It 'Should convert a DatabaseFileGroupSpec with multiple file properties to a FileGroup object' { $fileSpec1 = New-SqlDscDataFile -Name 'TestFile1' -FileName 'C:\Data\TestFile1.ndf' -Size 100 -MaxSize 1000 -AsSpec $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\Data\TestFile2.ndf' -Growth 10 -GrowthType 'Percent' -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec1, $fileSpec2) -ReadOnly $false -IsDefault $false -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'TestFileGroup' -Files @($fileSpec1, $fileSpec2) -AsSpec $result = ConvertTo-SqlDscFileGroup -DatabaseObject $script:testDatabase -FileGroupSpec $fileGroupSpec @@ -125,14 +125,12 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_ $result.Files[0].MaxSize | Should -Be 1000 $result.Files[1].Growth | Should -Be 10 $result.Files[1].GrowthType | Should -Be 'Percent' - $result.ReadOnly | Should -Be $false - $result.IsDefault | Should -Be $false } It 'Should preserve file properties when converting FileGroup with complex file configurations' { - $primaryFile = New-SqlDscDataFile -Name 'PrimaryFile' -FileName 'C:\Data\Primary.mdf' -IsPrimaryFile $true -Size 200 -MaxSize 2000 -Growth 20 -GrowthType 'KB' -AsSpec + $primaryFile = New-SqlDscDataFile -Name 'PrimaryFile' -FileName 'C:\Data\Primary.mdf' -IsPrimaryFile -Size 200 -MaxSize 2000 -Growth 20 -GrowthType 'KB' -AsSpec $secondaryFile = New-SqlDscDataFile -Name 'SecondaryFile' -FileName 'C:\Data\Secondary.ndf' -Size 100 -MaxSize 1000 -Growth 10 -GrowthType 'Percent' -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'ComplexFileGroup' -Files @($primaryFile, $secondaryFile) -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile, $secondaryFile) -AsSpec $result = ConvertTo-SqlDscFileGroup -DatabaseObject $script:testDatabase -FileGroupSpec $fileGroupSpec diff --git a/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 index edbf2eefd1..91f1791aca 100644 --- a/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 @@ -181,7 +181,7 @@ Describe 'New-SqlDscDatabase' -Tag @('Integration_SQL2017', 'Integration_SQL2019 It 'Should create a database with custom file groups and data files' { # Create PRIMARY filegroup specification with data file using -AsSpec parameters - $primaryFile = New-SqlDscDataFile -Name ($script:testDatabaseWithFileGroups + '_Primary') -FileName (Join-Path -Path $script:dataDirectory -ChildPath ($script:testDatabaseWithFileGroups + '_Primary.mdf')) -IsPrimaryFile $true -AsSpec + $primaryFile = New-SqlDscDataFile -Name ($script:testDatabaseWithFileGroups + '_Primary') -FileName (Join-Path -Path $script:dataDirectory -ChildPath ($script:testDatabaseWithFileGroups + '_Primary.mdf')) -IsPrimaryFile -AsSpec $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -AsSpec # Create a secondary filegroup specification with data file using -AsSpec parameters diff --git a/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 index c469a4af51..18dd4b63b7 100644 --- a/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 @@ -81,7 +81,7 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag 'Public' { param ($mockFileGroup) $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' ` - -Size 102400 -MaxSize 512000 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile $true -AsSpec + -Size 102400 -MaxSize 512000 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec @@ -158,7 +158,7 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag 'Public' { } -ScriptBlock { param ($mockFileGroup) - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -IsPrimaryFile $true -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -IsPrimaryFile -AsSpec $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec diff --git a/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 index e7d640a644..05555ac582 100644 --- a/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 @@ -79,7 +79,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag 'Public' { } -ScriptBlock { param ($mockDatabase) - $fileGroupSpec = New-SqlDscFileGroup -Name 'READONLY_FG' -ReadOnly $true -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'READONLY_FG' -ReadOnly -AsSpec $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec @@ -94,7 +94,7 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag 'Public' { } -ScriptBlock { param ($mockDatabase) - $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -IsDefault $true -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -IsDefault -AsSpec $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec @@ -149,18 +149,17 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag 'Public' { param ($mockDatabase) $primaryFile = New-SqlDscDataFile -Name 'PrimaryFile' -FileName 'C:\SQLData\Primary.mdf' ` - -Size 102400 -MaxSize 512000 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile $true -AsSpec + -Size 102400 -MaxSize 512000 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec $secondaryFile = New-SqlDscDataFile -Name 'SecondaryFile' -FileName 'C:\SQLData\Secondary.ndf' ` -Size 204800 -MaxSize 1024000 -Growth 20480 -GrowthType 'KB' -AsSpec $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile, $secondaryFile) ` - -ReadOnly $false -IsDefault $true -AsSpec + -IsDefault -AsSpec $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec $result.Name | Should -Be 'PRIMARY' - $result.ReadOnly | Should -Be $false $result.IsDefault | Should -Be $true $result.Files.Count | Should -Be 2 $result.Files[0].Name | Should -Be 'PrimaryFile' diff --git a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 index 9da065fee2..460b625cc6 100644 --- a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 @@ -203,4 +203,100 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { ForEach-Object { $_.ConfirmImpact } | Should -Be 'High' } } + + Context 'When creating a DataFile with AsSpec parameter set' { + It 'Should return a DatabaseFileSpec object' { + InModuleScope -ScriptBlock { + $result = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -AsSpec + + $result | Should -Not -BeNullOrEmpty + $result.GetType().Name | Should -Be 'DatabaseFileSpec' + $result.Name | Should -Be 'MyDB_Primary' + $result.FileName | Should -Be 'D:\SQLData\MyDB.mdf' + } + } + + It 'Should set IsPrimaryFile when specified' { + InModuleScope -ScriptBlock { + $result = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -IsPrimaryFile -AsSpec + + $result | Should -Not -BeNullOrEmpty + $result.IsPrimaryFile | Should -BeTrue + } + } + + It 'Should set Size, MaxSize, Growth, and GrowthType when specified' { + InModuleScope -ScriptBlock { + $result = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -Size 102400 -MaxSize 5242880 -Growth 10240 -GrowthType 'KB' -AsSpec + + $result | Should -Not -BeNullOrEmpty + $result.Size | Should -Be 102400 + $result.MaxSize | Should -Be 5242880 + $result.Growth | Should -Be 10240 + $result.GrowthType | Should -Be 'KB' + } + } + } + + Context 'When creating a DataFile from a DatabaseFileSpec' { + BeforeAll { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject.Name = 'TestDatabase' + } + + It 'Should create a DataFile from a DatabaseFileSpec in the PRIMARY filegroup' { + InModuleScope -Parameters @{ + mockDatabaseObject = $mockDatabaseObject + } -ScriptBlock { + param ($mockDatabaseObject) + + $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'PRIMARY' + + $fileSpec = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -IsPrimaryFile -AsSpec + + $initialFileCount = $mockFileGroupObject.Files.Count + + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFileSpec $fileSpec -PassThru -Force + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) + } + } + + It 'Should throw an error when IsPrimaryFile is specified but filegroup is not PRIMARY' { + InModuleScope -Parameters @{ + mockDatabaseObject = $mockDatabaseObject + } -ScriptBlock { + param ($mockDatabaseObject) + + $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'SECONDARY' + + $fileSpec = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -IsPrimaryFile -AsSpec + + { New-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFileSpec $fileSpec -Force -ErrorAction Stop } | + Should -Throw -ExpectedMessage '*The primary file must reside in the PRIMARY filegroup*' + } + } + + It 'Should create a DataFile from a DatabaseFileSpec without IsPrimaryFile in a non-PRIMARY filegroup' { + InModuleScope -Parameters @{ + mockDatabaseObject = $mockDatabaseObject + } -ScriptBlock { + param ($mockDatabaseObject) + + $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'SECONDARY' + + $fileSpec = New-SqlDscDataFile -Name 'MyDB_Secondary' -FileName 'D:\SQLData\MyDB_Secondary.ndf' -AsSpec + + $initialFileCount = $mockFileGroupObject.Files.Count + + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFileSpec $fileSpec -PassThru -Force + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) + } + } + } } diff --git a/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 b/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 index 43c45ceea0..4c1aec031e 100644 --- a/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 @@ -351,7 +351,7 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { mockServerObject = $mockServerObject } -ScriptBlock { # Create file spec with parameters using -AsSpec - $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile $true -AsSpec + $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec # Create filegroup spec with parameters using -AsSpec $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault $true -AsSpec @@ -371,7 +371,7 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { mockServerObject = $mockServerObject } -ScriptBlock { # Create PRIMARY filegroup - $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -IsPrimaryFile $true -AsSpec + $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -IsPrimaryFile -AsSpec $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault $true -AsSpec # Create secondary filegroup From 103a80b597d3462d1b6f8675c53f05a14c9801d4 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 14:49:21 +0100 Subject: [PATCH 46/87] Fix IsDefault parameter usage in New-SqlDscFileGroup calls in database snapshot tests --- tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 b/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 index 4c1aec031e..855ea971ee 100644 --- a/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 @@ -354,7 +354,7 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec # Create filegroup spec with parameters using -AsSpec - $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault $true -AsSpec + $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault -AsSpec $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestDB' -FileGroup @($primaryFileGroup) -Force @@ -372,7 +372,7 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { } -ScriptBlock { # Create PRIMARY filegroup $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -IsPrimaryFile -AsSpec - $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault $true -AsSpec + $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault -AsSpec # Create secondary filegroup $secondaryFile = New-SqlDscDataFile -Name 'TestDB_Secondary' -FileName 'E:\SQLData\TestDB.ndf' -Size 204800 -AsSpec From 80abe5cc400bf2be9a577a14f5a168fe07068010 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 16:48:23 +0100 Subject: [PATCH 47/87] Add FileGroup parameter to New-SqlDscDatabaseSnapshot and update tests --- azure-pipelines.yml | 1 + source/Public/New-SqlDscDatabaseSnapshot.ps1 | 39 +++- ...lDscDatabaseSnapshot.Integration.Tests.ps1 | 191 ++++++++++++++++++ tests/Integration/Commands/README.md | 1 + .../New-SqlDscDatabaseSnapshot.Tests.ps1 | 27 ++- 5 files changed, 247 insertions(+), 12 deletions(-) create mode 100644 tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f782a2e3dc..207a78d9ea 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -340,6 +340,7 @@ stages: 'tests/Integration/Commands/Get-SqlDscDatabase.Integration.Tests.ps1' 'tests/Integration/Commands/ConvertFrom-SqlDscDatabasePermission.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1' + 'tests/Integration/Commands/Get-SqlDscCompatibilityLevel.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscDatabaseProperty.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscDatabaseOwner.Integration.Tests.ps1' diff --git a/source/Public/New-SqlDscDatabaseSnapshot.ps1 b/source/Public/New-SqlDscDatabaseSnapshot.ps1 index de117b90b4..2c63f8201f 100644 --- a/source/Public/New-SqlDscDatabaseSnapshot.ps1 +++ b/source/Public/New-SqlDscDatabaseSnapshot.ps1 @@ -23,6 +23,13 @@ Specifies the source database object to snapshot. This parameter can be provided via pipeline and is used in the DatabaseObject parameter set. + .PARAMETER FileGroup + Specifies an array of DatabaseFileGroupSpec objects that define the file groups + and data files for the database snapshot. Each DatabaseFileGroupSpec contains the + file group name and an array of DatabaseFileSpec objects for the sparse files. + When not specified, SQL Server will create sparse files in the default data + directory with automatically generated names. + .PARAMETER Force Specifies that the snapshot should be created without any confirmation. @@ -55,6 +62,18 @@ Creates a new database snapshot named **MyDB_Snap** from the source database **MyDatabase** without prompting for confirmation. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sourceDb = $serverObject.Databases['MyDatabase'] + + $dataFile = New-SqlDscDataFile -Name 'MyDatabase_Data' -FileName 'C:\Snapshots\MyDatabase_Data.ss' -AsSpec + $fileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($dataFile) -AsSpec + + $serverObject | New-SqlDscDatabaseSnapshot -Name 'MyDB_Snap' -DatabaseName 'MyDatabase' -FileGroup @($fileGroup) -Force + + Creates a new database snapshot named **MyDB_Snap** from the source database + **MyDatabase** with a specified sparse file location without prompting for confirmation. + .INPUTS `[Microsoft.SqlServer.Management.Smo.Server]` @@ -73,6 +92,7 @@ #> function New-SqlDscDatabaseSnapshot { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'Because ShouldProcess is used in New-SqlDscDatabase')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [OutputType([Microsoft.SqlServer.Management.Smo.Database])] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] @@ -96,6 +116,10 @@ function New-SqlDscDatabaseSnapshot [System.String] $DatabaseName, + [Parameter()] + [DatabaseFileGroupSpec[]] + $FileGroup, + [Parameter()] [System.Management.Automation.SwitchParameter] $Force, @@ -170,22 +194,17 @@ function New-SqlDscDatabaseSnapshot Name = $Name DatabaseSnapshotBaseName = $DatabaseName Force = $Force + WhatIf = $WhatIfPreference } - if ($PSCmdlet.ParameterSetName -eq 'ServerObject' -and $Refresh.IsPresent) - { - $newSqlDscDatabaseParameters['Refresh'] = $true - } - - # Use WhatIf and Confirm from the current cmdlet context - if ($PSBoundParameters.ContainsKey('WhatIf')) + if ($PSBoundParameters.ContainsKey('FileGroup')) { - $newSqlDscDatabaseParameters['WhatIf'] = $WhatIf + $newSqlDscDatabaseParameters['FileGroup'] = $FileGroup } - if ($PSBoundParameters.ContainsKey('Confirm')) + if ($PSCmdlet.ParameterSetName -eq 'ServerObject' -and $Refresh.IsPresent) { - $newSqlDscDatabaseParameters['Confirm'] = $Confirm + $newSqlDscDatabaseParameters['Refresh'] = $true } $snapshotDatabaseObject = New-SqlDscDatabase @newSqlDscDatabaseParameters diff --git a/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 new file mode 100644 index 0000000000..fd6efac0c5 --- /dev/null +++ b/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 @@ -0,0 +1,191 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +Describe 'New-SqlDscDatabaseSnapshot' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + + # Source database names - using the persistent database created by New-SqlDscDatabase integration tests + $script:persistentSourceDatabase = 'SqlDscIntegrationTestDatabase_Persistent' + + # Snapshot names + $script:testSnapshotName = 'SqlDscTestSnapshot_' + (Get-Random) + $script:testSnapshotNameWithFileGroup = 'SqlDscTestSnapshotFG_' + (Get-Random) + $script:testSnapshotNameFromDbObject = 'SqlDscTestSnapshotDbObj_' + (Get-Random) + + # Verify the persistent database exists before proceeding + $sourceDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentSourceDatabase -ErrorAction 'SilentlyContinue' + + if (-not $sourceDb) + { + throw "The source database '$script:persistentSourceDatabase' does not exist. Please ensure New-SqlDscDatabase integration tests have run successfully." + } + } + + AfterAll { + # Clean up test snapshots + $testSnapshotsToRemove = @( + $script:testSnapshotName, + $script:testSnapshotNameWithFileGroup, + $script:testSnapshotNameFromDbObject + ) + + foreach ($snapshotName in $testSnapshotsToRemove) + { + $existingSnapshot = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $snapshotName -ErrorAction 'SilentlyContinue' + + if ($existingSnapshot) + { + $null = Remove-SqlDscDatabase -DatabaseObject $existingSnapshot -Force -ErrorAction 'Stop' + } + } + + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + } + + Context 'When creating a database snapshot using ServerObject parameter set' { + It 'Should create a database snapshot successfully with minimal parameters' { + $result = New-SqlDscDatabaseSnapshot -ServerObject $script:serverObject -Name $script:testSnapshotName -DatabaseName $script:persistentSourceDatabase -Force -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be $script:testSnapshotName + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Database' + $result.DatabaseSnapshotBaseName | Should -Be $script:persistentSourceDatabase + + # Verify the snapshot exists + $createdSnapshot = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testSnapshotName -Refresh -ErrorAction 'Stop' + $createdSnapshot | Should -Not -BeNullOrEmpty + $createdSnapshot.DatabaseSnapshotBaseName | Should -Be $script:persistentSourceDatabase + } + + It 'Should throw error when trying to create a snapshot that already exists' { + { New-SqlDscDatabaseSnapshot -ServerObject $script:serverObject -Name $script:testSnapshotName -DatabaseName $script:persistentSourceDatabase -Force -ErrorAction 'Stop' } | + Should -Throw + } + } + + Context 'When creating a database snapshot using DatabaseObject parameter set' { + BeforeAll { + # Get the source database object + $script:sourceDatabaseObject = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentSourceDatabase -ErrorAction 'Stop' + } + + It 'Should create a database snapshot from DatabaseObject successfully' { + $result = New-SqlDscDatabaseSnapshot -DatabaseObject $script:sourceDatabaseObject -Name $script:testSnapshotNameFromDbObject -Force -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be $script:testSnapshotNameFromDbObject + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Database' + $result.DatabaseSnapshotBaseName | Should -Be $script:persistentSourceDatabase + + # Verify the snapshot exists + $createdSnapshot = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testSnapshotNameFromDbObject -Refresh -ErrorAction 'Stop' + $createdSnapshot | Should -Not -BeNullOrEmpty + $createdSnapshot.DatabaseSnapshotBaseName | Should -Be $script:persistentSourceDatabase + } + } + + Context 'When creating a database snapshot with custom file groups' { + BeforeAll { + # Get the default data directory from the server + $script:dataDirectory = $script:serverObject.Settings.DefaultFile + + if (-not $script:dataDirectory) + { + $script:dataDirectory = $script:serverObject.Information.MasterDBPath + } + + # Ensure the directory exists + if (-not (Test-Path -Path $script:dataDirectory)) + { + $null = New-Item -Path $script:dataDirectory -ItemType Directory -Force + } + + # Get the source database for file group creation + $script:sourceDatabaseForFG = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentSourceDatabase -ErrorAction 'Stop' + } + + It 'Should create a database snapshot with custom sparse file location' { + # Create PRIMARY filegroup with sparse file using -AsSpec + $sparseFilePath = Join-Path -Path $script:dataDirectory -ChildPath ($script:testSnapshotNameWithFileGroup + '_Sparse.ss') + $dataFileSpec = New-SqlDscDataFile -Name ($script:testSnapshotNameWithFileGroup + '_Sparse') -FileName $sparseFilePath -AsSpec + $primaryFileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($dataFileSpec) -AsSpec + + # Create snapshot with custom file group + $result = New-SqlDscDatabaseSnapshot -ServerObject $script:serverObject -Name $script:testSnapshotNameWithFileGroup -DatabaseName $script:persistentSourceDatabase -FileGroup @($primaryFileGroupSpec) -Force -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be $script:testSnapshotNameWithFileGroup + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Database' + $result.DatabaseSnapshotBaseName | Should -Be $script:persistentSourceDatabase + + # Verify the snapshot exists with correct file configuration + $createdSnapshot = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testSnapshotNameWithFileGroup -Refresh -ErrorAction 'Stop' + $createdSnapshot | Should -Not -BeNullOrEmpty + $createdSnapshot.DatabaseSnapshotBaseName | Should -Be $script:persistentSourceDatabase + + # Verify the sparse file was created + $createdSnapshot.FileGroups['PRIMARY'] | Should -Not -BeNullOrEmpty + $createdSnapshot.FileGroups['PRIMARY'].Files.Count | Should -BeGreaterThan 0 + } + } + + Context 'When using the Refresh parameter' { + BeforeAll { + $script:refreshTestSnapshotName = 'SqlDscTestSnapshotRefresh_' + (Get-Random) + } + + AfterAll { + # Clean up the refresh test snapshot + $snapshotToRemove = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:refreshTestSnapshotName -ErrorAction 'SilentlyContinue' + if ($snapshotToRemove) + { + $null = Remove-SqlDscDatabase -DatabaseObject $snapshotToRemove -Force -ErrorAction 'Stop' + } + } + + It 'Should refresh the database collection before creating snapshot' { + $result = New-SqlDscDatabaseSnapshot -ServerObject $script:serverObject -Name $script:refreshTestSnapshotName -DatabaseName $script:persistentSourceDatabase -Refresh -Force -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be $script:refreshTestSnapshotName + $result.DatabaseSnapshotBaseName | Should -Be $script:persistentSourceDatabase + } + } +} diff --git a/tests/Integration/Commands/README.md b/tests/Integration/Commands/README.md index 27f0896d39..224a6bfbde 100644 --- a/tests/Integration/Commands/README.md +++ b/tests/Integration/Commands/README.md @@ -91,6 +91,7 @@ Revoke-SqlDscServerPermission | 4 | 4 (New-SqlDscLogin), 1 (Install-SqlDscServer Get-SqlDscDatabase | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - ConvertFrom-SqlDscDatabasePermission | 4 | 0 (Prerequisites) | - | - New-SqlDscDatabase | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | SqlDscIntegrationTestDatabase_Persistent database +New-SqlDscDatabaseSnapshot | 5 | 4 (New-SqlDscDatabase), 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Get-SqlDscCompatibilityLevel | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Set-SqlDscDatabaseProperty | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Set-SqlDscDatabaseOwner | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - diff --git a/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 b/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 index d37c9b4810..384edfdc45 100644 --- a/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 @@ -105,6 +105,29 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $Refresh -eq $true } -Exactly -Times 1 } + + It 'Should pass FileGroup parameter when specified' { + InModuleScope -Parameters @{ + mockServerObject = $mockServerObject + } -ScriptBlock { + # Create a mock DatabaseFileGroupSpec + $mockDataFileSpec = [DatabaseFileSpec]@{ + Name = 'TestData' + FileName = 'C:\Snapshots\TestData.ss' + } + + $mockFileGroupSpec = [DatabaseFileGroupSpec]@{ + Name = 'PRIMARY' + Files = @($mockDataFileSpec) + } + + $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -FileGroup @($mockFileGroupSpec) -Force + + Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { + $FileGroup -and $FileGroup.Count -eq 1 -and $FileGroup[0].Name -eq 'PRIMARY' + } -Exactly -Times 1 + } + } } Context 'When creating a database snapshot using DatabaseObject parameter set' { @@ -235,7 +258,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { It 'Should have the correct parameters in parameter set ServerObject' -ForEach @( @{ ExpectedParameterSetName = 'ServerObject' - ExpectedParameters = '-ServerObject -Name -DatabaseName [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + ExpectedParameters = '-ServerObject -Name -DatabaseName [-FileGroup ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscDatabaseSnapshot').ParameterSets | @@ -252,7 +275,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { It 'Should have the correct parameters in parameter set DatabaseObject' -ForEach @( @{ ExpectedParameterSetName = 'DatabaseObject' - ExpectedParameters = '-DatabaseObject -Name [-Force] [-WhatIf] [-Confirm] []' + ExpectedParameters = '-DatabaseObject -Name [-FileGroup ] [-Force] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscDatabaseSnapshot').ParameterSets | From 8cc7a1651d77263d12618f5063ec91c4167631f2 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 17:54:54 +0100 Subject: [PATCH 48/87] Add New-SqlDscDatabaseSnapshot integration tests to pipeline --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 207a78d9ea..c73555db99 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -340,7 +340,7 @@ stages: 'tests/Integration/Commands/Get-SqlDscDatabase.Integration.Tests.ps1' 'tests/Integration/Commands/ConvertFrom-SqlDscDatabasePermission.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1' - + 'tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscCompatibilityLevel.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscDatabaseProperty.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscDatabaseOwner.Integration.Tests.ps1' From 714ec411ecaadfd094281f7d2b2b8f5f099d4b57 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 17:59:09 +0100 Subject: [PATCH 49/87] Remove test for Database parameter pipeline acceptance in New-SqlDscFileGroup to prevent file path reuse --- tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 index a8c80ad432..8415ccebb4 100644 --- a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 @@ -89,12 +89,6 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { } } - It 'Should not accept Database parameter from pipeline (removed to prevent file path reuse)' { - # Verify that the parameter doesn't have ValueFromPipeline attribute - $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] - $parameterInfo.Attributes.ValueFromPipeline | Should -Not -Contain $true - } - It 'Should support Force parameter to bypass confirmation' { InModuleScope -Parameters @{ mockDatabaseObject = $mockDatabaseObject From 30293f3438812ad07000f40a6d5cd3230ce90e0b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 17:59:21 +0100 Subject: [PATCH 50/87] Add -Confirm:$false parameter to Add-SqlDscFileGroup to suppress confirmation prompt --- source/Public/New-SqlDscDatabase.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Public/New-SqlDscDatabase.ps1 b/source/Public/New-SqlDscDatabase.ps1 index 002c2ee585..d3a973beb4 100644 --- a/source/Public/New-SqlDscDatabase.ps1 +++ b/source/Public/New-SqlDscDatabase.ps1 @@ -323,7 +323,7 @@ function New-SqlDscDatabase $smoFileGroup = New-SqlDscFileGroup -Database $sqlDatabaseObjectToCreate -FileGroupSpec $fileGroupSpec -Force # Add the file group to the database - Add-SqlDscFileGroup -Database $sqlDatabaseObjectToCreate -FileGroup $smoFileGroup + Add-SqlDscFileGroup -Database $sqlDatabaseObjectToCreate -FileGroup $smoFileGroup -Confirm:$false } } From 5006eaefb5e47b2b6cddd0b59dd38809951b42f0 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:00:13 +0100 Subject: [PATCH 51/87] Remove test for Database parameter pipeline input acceptance in New-SqlDscFileGroup --- tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 index 8415ccebb4..e3797e6eb9 100644 --- a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 @@ -166,11 +166,6 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { $parameterInfo.Attributes.Mandatory | Should -Contain $true } - It 'Should not have Database parameter accept pipeline input' { - $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] - $parameterInfo.Attributes.ValueFromPipeline | Should -Not -Contain $true - } - It 'Should have three parameter sets (WithDatabase, WithDatabaseFromSpec, AsSpec)' { $command = Get-Command -Name 'New-SqlDscFileGroup' $command.ParameterSets.Count | Should -Be 3 From 3a461fe9d7977dc1280fd2db845f6282abbe833e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:03:51 +0100 Subject: [PATCH 52/87] Refactor tests in ConvertTo-SqlDscDataFile and ConvertTo-SqlDscFileGroup to remove InModuleScope and streamline assertions --- .../Public/ConvertTo-SqlDscDataFile.Tests.ps1 | 110 +++++--------- .../ConvertTo-SqlDscFileGroup.Tests.ps1 | 140 ++++++------------ 2 files changed, 83 insertions(+), 167 deletions(-) diff --git a/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 index 18dd4b63b7..57113832d8 100644 --- a/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/ConvertTo-SqlDscDataFile.Tests.ps1 @@ -58,112 +58,70 @@ Describe 'ConvertTo-SqlDscDataFile' -Tag 'Public' { } It 'Should convert a basic file spec with only required properties' { - InModuleScope -Parameters @{ - mockFileGroup = $mockFileGroup - } -ScriptBlock { - param ($mockFileGroup) + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -AsSpec - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -AsSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec - $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $result.Name | Should -Be 'TestFile' - $result.FileName | Should -Be 'C:\SQLData\TestFile.mdf' - } + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'TestFile' + $result.FileName | Should -Be 'C:\SQLData\TestFile.mdf' } It 'Should convert a file spec with all optional properties set' { - InModuleScope -Parameters @{ - mockFileGroup = $mockFileGroup - } -ScriptBlock { - param ($mockFileGroup) - - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' ` - -Size 102400 -MaxSize 512000 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec - - $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec - - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'TestFile' - $result.FileName | Should -Be 'C:\SQLData\TestFile.mdf' - $result.Size | Should -Be 102400 - $result.MaxSize | Should -Be 512000 - $result.Growth | Should -Be 10240 - $result.GrowthType | Should -Be 'KB' - $result.IsPrimaryFile | Should -Be $true - } + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' ` + -Size 102400 -MaxSize 512000 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec + + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestFile' + $result.FileName | Should -Be 'C:\SQLData\TestFile.mdf' + $result.Size | Should -Be 102400 + $result.MaxSize | Should -Be 512000 + $result.Growth | Should -Be 10240 + $result.GrowthType | Should -Be 'KB' + $result.IsPrimaryFile | Should -Be $true } It 'Should convert a file spec with Size property' { - InModuleScope -Parameters @{ - mockFileGroup = $mockFileGroup - } -ScriptBlock { - param ($mockFileGroup) - - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -Size 204800 -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -Size 204800 -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec - $result.Size | Should -Be 204800 - } + $result.Size | Should -Be 204800 } It 'Should convert a file spec with MaxSize property' { - InModuleScope -Parameters @{ - mockFileGroup = $mockFileGroup - } -ScriptBlock { - param ($mockFileGroup) - - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -MaxSize 1024000 -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -MaxSize 1024000 -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec - $result.MaxSize | Should -Be 1024000 - } + $result.MaxSize | Should -Be 1024000 } It 'Should convert a file spec with Growth property' { - InModuleScope -Parameters @{ - mockFileGroup = $mockFileGroup - } -ScriptBlock { - param ($mockFileGroup) + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -Growth 20480 -AsSpec - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -Growth 20480 -AsSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec - $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec - - $result.Growth | Should -Be 20480 - } + $result.Growth | Should -Be 20480 } It 'Should convert a file spec with GrowthType property set to Percent' { - InModuleScope -Parameters @{ - mockFileGroup = $mockFileGroup - } -ScriptBlock { - param ($mockFileGroup) - - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -GrowthType 'Percent' -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -GrowthType 'Percent' -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec - $result.GrowthType | Should -Be 'Percent' - } + $result.GrowthType | Should -Be 'Percent' } It 'Should convert a file spec with IsPrimaryFile property' { - InModuleScope -Parameters @{ - mockFileGroup = $mockFileGroup - } -ScriptBlock { - param ($mockFileGroup) - - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -IsPrimaryFile -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -IsPrimaryFile -AsSpec - $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec + $result = ConvertTo-SqlDscDataFile -FileGroupObject $mockFileGroup -DataFileSpec $fileSpec - $result.IsPrimaryFile | Should -Be $true - } + $result.IsPrimaryFile | Should -Be $true } } diff --git a/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 index 05555ac582..37239e0639 100644 --- a/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 @@ -58,129 +58,87 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag 'Public' { } It 'Should convert a basic file group spec with only required properties' { - InModuleScope -Parameters @{ - mockDatabase = $mockDatabase - } -ScriptBlock { - param ($mockDatabase) + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -AsSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec - $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' - $result.Name | Should -Be 'PRIMARY' - } + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'PRIMARY' } It 'Should convert a file group spec with ReadOnly property' { - InModuleScope -Parameters @{ - mockDatabase = $mockDatabase - } -ScriptBlock { - param ($mockDatabase) + $fileGroupSpec = New-SqlDscFileGroup -Name 'READONLY_FG' -ReadOnly -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'READONLY_FG' -ReadOnly -AsSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec - $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec - - $result.Name | Should -Be 'READONLY_FG' - $result.ReadOnly | Should -Be $true - } + $result.Name | Should -Be 'READONLY_FG' + $result.ReadOnly | Should -Be $true } It 'Should convert a file group spec with IsDefault property' { - InModuleScope -Parameters @{ - mockDatabase = $mockDatabase - } -ScriptBlock { - param ($mockDatabase) + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -IsDefault -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -IsDefault -AsSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec - $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec - - $result.Name | Should -Be 'PRIMARY' - $result.IsDefault | Should -Be $true - } + $result.Name | Should -Be 'PRIMARY' + $result.IsDefault | Should -Be $true } It 'Should convert a file group spec with a single data file' { - InModuleScope -Parameters @{ - mockDatabase = $mockDatabase - } -ScriptBlock { - param ($mockDatabase) - - $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($fileSpec) -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'TestFile' -FileName 'C:\SQLData\TestFile.mdf' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($fileSpec) -AsSpec - $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec - $result.Name | Should -Be 'PRIMARY' - $result.Files.Count | Should -Be 1 - $result.Files[0].Name | Should -Be 'TestFile' - $result.Files[0].FileName | Should -Be 'C:\SQLData\TestFile.mdf' - } + $result.Name | Should -Be 'PRIMARY' + $result.Files.Count | Should -Be 1 + $result.Files[0].Name | Should -Be 'TestFile' + $result.Files[0].FileName | Should -Be 'C:\SQLData\TestFile.mdf' } It 'Should convert a file group spec with multiple data files' { - InModuleScope -Parameters @{ - mockDatabase = $mockDatabase - } -ScriptBlock { - param ($mockDatabase) - - $fileSpec1 = New-SqlDscDataFile -Name 'TestFile1' -FileName 'C:\SQLData\TestFile1.ndf' -Size 102400 -AsSpec - $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\SQLData\TestFile2.ndf' -Size 204800 -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'SECONDARY' -Files @($fileSpec1, $fileSpec2) -AsSpec - - $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec - - $result.Name | Should -Be 'SECONDARY' - $result.Files.Count | Should -Be 2 - $result.Files[0].Name | Should -Be 'TestFile1' - $result.Files[0].Size | Should -Be 102400 - $result.Files[1].Name | Should -Be 'TestFile2' - $result.Files[1].Size | Should -Be 204800 - } + $fileSpec1 = New-SqlDscDataFile -Name 'TestFile1' -FileName 'C:\SQLData\TestFile1.ndf' -Size 102400 -AsSpec + $fileSpec2 = New-SqlDscDataFile -Name 'TestFile2' -FileName 'C:\SQLData\TestFile2.ndf' -Size 204800 -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'SECONDARY' -Files @($fileSpec1, $fileSpec2) -AsSpec + + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + + $result.Name | Should -Be 'SECONDARY' + $result.Files.Count | Should -Be 2 + $result.Files[0].Name | Should -Be 'TestFile1' + $result.Files[0].Size | Should -Be 102400 + $result.Files[1].Name | Should -Be 'TestFile2' + $result.Files[1].Size | Should -Be 204800 } It 'Should convert a file group spec with all properties and multiple files' { - InModuleScope -Parameters @{ - mockDatabase = $mockDatabase - } -ScriptBlock { - param ($mockDatabase) - - $primaryFile = New-SqlDscDataFile -Name 'PrimaryFile' -FileName 'C:\SQLData\Primary.mdf' ` - -Size 102400 -MaxSize 512000 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec + $primaryFile = New-SqlDscDataFile -Name 'PrimaryFile' -FileName 'C:\SQLData\Primary.mdf' ` + -Size 102400 -MaxSize 512000 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec - $secondaryFile = New-SqlDscDataFile -Name 'SecondaryFile' -FileName 'C:\SQLData\Secondary.ndf' ` - -Size 204800 -MaxSize 1024000 -Growth 20480 -GrowthType 'KB' -AsSpec + $secondaryFile = New-SqlDscDataFile -Name 'SecondaryFile' -FileName 'C:\SQLData\Secondary.ndf' ` + -Size 204800 -MaxSize 1024000 -Growth 20480 -GrowthType 'KB' -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile, $secondaryFile) ` - -IsDefault -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile, $secondaryFile) ` + -IsDefault -AsSpec - $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec - $result.Name | Should -Be 'PRIMARY' - $result.IsDefault | Should -Be $true - $result.Files.Count | Should -Be 2 - $result.Files[0].Name | Should -Be 'PrimaryFile' - $result.Files[0].IsPrimaryFile | Should -Be $true - $result.Files[1].Name | Should -Be 'SecondaryFile' - } + $result.Name | Should -Be 'PRIMARY' + $result.IsDefault | Should -Be $true + $result.Files.Count | Should -Be 2 + $result.Files[0].Name | Should -Be 'PrimaryFile' + $result.Files[0].IsPrimaryFile | Should -Be $true + $result.Files[1].Name | Should -Be 'SecondaryFile' } It 'Should convert a file group spec without files (empty Files array)' { - InModuleScope -Parameters @{ - mockDatabase = $mockDatabase - } -ScriptBlock { - param ($mockDatabase) - - $fileGroupSpec = New-SqlDscFileGroup -Name 'EMPTY_FG' -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'EMPTY_FG' -AsSpec - $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec + $result = ConvertTo-SqlDscFileGroup -DatabaseObject $mockDatabase -FileGroupSpec $fileGroupSpec - $result.Name | Should -Be 'EMPTY_FG' - $result.Files.Count | Should -Be 0 - } + $result.Name | Should -Be 'EMPTY_FG' + $result.Files.Count | Should -Be 0 } } From 434b68c47af5507979f6cf99673d1cd970ab281e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:05:46 +0100 Subject: [PATCH 53/87] Remove unused mockComputerName variable and enhance snapshot file creation verification in New-SqlDscDatabaseSnapshot tests --- .../Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 index fd6efac0c5..514d773466 100644 --- a/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 @@ -32,7 +32,6 @@ BeforeAll { Describe 'New-SqlDscDatabaseSnapshot' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { BeforeAll { $script:mockInstanceName = 'DSCSQLTEST' - $script:mockComputerName = Get-ComputerName $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force @@ -160,9 +159,10 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag @('Integration_SQL2017', 'Integration $createdSnapshot | Should -Not -BeNullOrEmpty $createdSnapshot.DatabaseSnapshotBaseName | Should -Be $script:persistentSourceDatabase - # Verify the sparse file was created + # Verify the sparse file was created with the correct path $createdSnapshot.FileGroups['PRIMARY'] | Should -Not -BeNullOrEmpty $createdSnapshot.FileGroups['PRIMARY'].Files.Count | Should -BeGreaterThan 0 + $createdSnapshot.FileGroups['PRIMARY'].Files[0].FileName | Should -Be $sparseFilePath } } From d8e8d4119fc58ab7c32d00f245faeaa94c905e1e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:15:29 +0100 Subject: [PATCH 54/87] Add guideline to avoid using param() inside -MockWith scriptblocks --- .../dsc-community-style-guidelines-pester.instructions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md index 08ef42c190..a25b3d95d8 100644 --- a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md @@ -46,6 +46,7 @@ applyTo: "**/*.[Tt]ests.ps1" - Set `$PSDefaultParameterValues` for `Mock:ModuleName`, `Should:ModuleName`, `InModuleScope:ModuleName` - Omit `-ModuleName` parameter on Pester commands - Never use `Mock` inside `InModuleScope`-block +- Never use `param()` inside `-MockWith` scriptblocks, parameters are auto-bound ## File Organization - Class resources: `tests/Unit/Classes/{Name}.Tests.ps1` From 0078099684a178ba554c92fe6c07e129a3ff3d1a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:16:19 +0100 Subject: [PATCH 55/87] Add Force parameter to Add-SqlDscFileGroup and update related strings; modify New-SqlDscDatabase to use Force --- source/Public/Add-SqlDscFileGroup.ps1 | 29 +++++- source/Public/New-SqlDscDatabase.ps1 | 2 +- source/en-US/SqlServerDsc.strings.psd1 | 6 ++ .../Unit/Public/New-SqlDscDatabase.Tests.ps1 | 98 +++++++++++-------- 4 files changed, 90 insertions(+), 45 deletions(-) diff --git a/source/Public/Add-SqlDscFileGroup.ps1 b/source/Public/Add-SqlDscFileGroup.ps1 index 76b0d8e2e0..e8b08326b1 100644 --- a/source/Public/Add-SqlDscFileGroup.ps1 +++ b/source/Public/Add-SqlDscFileGroup.ps1 @@ -17,6 +17,9 @@ .PARAMETER PassThru Returns the FileGroup objects that were added to the Database. + .PARAMETER Force + Specifies that the FileGroup should be added without confirmation. + .OUTPUTS None, or [Microsoft.SqlServer.Management.Smo.FileGroup[]] if PassThru is specified. @@ -33,7 +36,7 @@ function Add-SqlDscFileGroup { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup[]])] param ( @@ -47,18 +50,34 @@ function Add-SqlDscFileGroup [Parameter()] [System.Management.Automation.SwitchParameter] - $PassThru + $PassThru, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force ) process { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + foreach ($fileGroupObject in $FileGroup) { - $Database.FileGroups.Add($fileGroupObject) + $descriptionMessage = $script:localizedData.AddSqlDscFileGroup_Add_ShouldProcessDescription -f $fileGroupObject.Name, $Database.Name + $confirmationMessage = $script:localizedData.AddSqlDscFileGroup_Add_ShouldProcessConfirmation -f $fileGroupObject.Name + $captionMessage = $script:localizedData.AddSqlDscFileGroup_Add_ShouldProcessCaption - if ($PassThru.IsPresent) + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) { - $fileGroupObject + $Database.FileGroups.Add($fileGroupObject) + + if ($PassThru.IsPresent) + { + $fileGroupObject + } } } } diff --git a/source/Public/New-SqlDscDatabase.ps1 b/source/Public/New-SqlDscDatabase.ps1 index d3a973beb4..59d234f6df 100644 --- a/source/Public/New-SqlDscDatabase.ps1 +++ b/source/Public/New-SqlDscDatabase.ps1 @@ -323,7 +323,7 @@ function New-SqlDscDatabase $smoFileGroup = New-SqlDscFileGroup -Database $sqlDatabaseObjectToCreate -FileGroupSpec $fileGroupSpec -Force # Add the file group to the database - Add-SqlDscFileGroup -Database $sqlDatabaseObjectToCreate -FileGroup $smoFileGroup -Confirm:$false + Add-SqlDscFileGroup -Database $sqlDatabaseObjectToCreate -FileGroup $smoFileGroup -Force } } diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index a55b6ca49e..3787af4c82 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -376,6 +376,12 @@ ConvertFrom-StringData @' FileGroup_Create_ShouldProcessCaption = Create filegroup for database FileGroup_DatabaseMissingServerObject = The Database object must have a Server object attached to the Parent property. (NSDFG0003) + ## Add-SqlDscFileGroup + AddSqlDscFileGroup_Add_ShouldProcessDescription = Adding the filegroup '{0}' to database '{1}'. (ASDFG0001) + AddSqlDscFileGroup_Add_ShouldProcessConfirmation = Are you sure you want to add the filegroup '{0}'? (ASDFG0002) + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + AddSqlDscFileGroup_Add_ShouldProcessCaption = Add filegroup to database + ## New-SqlDscDataFile DataFile_Create_ShouldProcessDescription = Creating the data file '{0}' for filegroup '{1}'. (NSDDF0001) DataFile_Create_ShouldProcessConfirmation = Are you sure you want to create the data file '{0}'? (NSDDF0002) diff --git a/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 b/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 index 855ea971ee..26e291244c 100644 --- a/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDatabase.Tests.ps1 @@ -292,25 +292,35 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { } Mock -CommandName 'Add-SqlDscFileGroup' + + # Mock helper commands used in tests + Mock -CommandName 'New-SqlDscDataFile' -MockWith { + return [DatabaseFileSpec]@{ + Name = $Name + FileName = $FileName + } + } + + Mock -CommandName 'New-SqlDscFileGroup' -ParameterFilter { $null -eq $FileGroupSpec } -MockWith { + return [DatabaseFileGroupSpec]@{ + Name = $Name + Files = $Files + } + } } It 'Should create a database snapshot with FileGroup spec successfully' { - InModuleScope -Parameters @{ - mockServerObject = $mockServerObject - } -ScriptBlock { - # Create file spec using New-SqlDscDataFile with -AsSpec - $fileSpec = New-SqlDscDataFile -Name 'TestSnapshot_Data' -FileName 'C:\Snapshots\TestSnapshot_Data.ss' -AsSpec + # Create file spec using New-SqlDscDataFile with -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'TestSnapshot_Data' -FileName 'C:\Snapshots\TestSnapshot_Data.ss' -AsSpec - # Create filegroup spec using New-SqlDscFileGroup with -AsSpec - $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($fileSpec) -AsSpec + # Create filegroup spec using New-SqlDscFileGroup with -AsSpec + $fileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($fileSpec) -AsSpec - $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseSnapshotBaseName 'SourceDatabase' -FileGroup @($fileGroupSpec) -Force - - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'TestSnapshot' - $result.DatabaseSnapshotBaseName | Should -Be 'SourceDatabase' - } + $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseSnapshotBaseName 'SourceDatabase' -FileGroup @($fileGroupSpec) -Force + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestSnapshot' + $result.DatabaseSnapshotBaseName | Should -Be 'SourceDatabase' Should -Invoke -CommandName 'New-SqlDscFileGroup' -Exactly -Times 1 -Scope It Should -Invoke -CommandName 'Add-SqlDscFileGroup' -Exactly -Times 1 -Scope It } @@ -344,46 +354,56 @@ Describe 'New-SqlDscDatabase' -Tag 'Public' { } Mock -CommandName 'Add-SqlDscFileGroup' + + # Mock helper commands used in tests + Mock -CommandName 'New-SqlDscDataFile' -MockWith { + return [DatabaseFileSpec]@{ + Name = $Name + FileName = $FileName + Size = $Size + Growth = $Growth + GrowthType = $GrowthType + IsPrimaryFile = $IsPrimaryFile.IsPresent + } + } + + Mock -CommandName 'New-SqlDscFileGroup' -ParameterFilter { $null -eq $FileGroupSpec } -MockWith { + return [DatabaseFileGroupSpec]@{ + Name = $Name + Files = $Files + IsDefault = $IsDefault.IsPresent + } + } } It 'Should create a database with custom PRIMARY filegroup using -AsSpec parameters' { - InModuleScope -Parameters @{ - mockServerObject = $mockServerObject - } -ScriptBlock { - # Create file spec with parameters using -AsSpec - $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec + # Create file spec with parameters using -AsSpec + $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -Growth 10240 -GrowthType 'KB' -IsPrimaryFile -AsSpec - # Create filegroup spec with parameters using -AsSpec - $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault -AsSpec + # Create filegroup spec with parameters using -AsSpec + $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault -AsSpec - $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestDB' -FileGroup @($primaryFileGroup) -Force - - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'TestDB' - } + $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestDB' -FileGroup @($primaryFileGroup) -Force + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestDB' Should -Invoke -CommandName 'New-SqlDscFileGroup' -Exactly -Times 1 -Scope It Should -Invoke -CommandName 'Add-SqlDscFileGroup' -Exactly -Times 1 -Scope It } It 'Should create a database with multiple filegroups using -AsSpec parameters' { - InModuleScope -Parameters @{ - mockServerObject = $mockServerObject - } -ScriptBlock { - # Create PRIMARY filegroup - $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -IsPrimaryFile -AsSpec - $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault -AsSpec + # Create PRIMARY filegroup + $primaryFile = New-SqlDscDataFile -Name 'TestDB_Primary' -FileName 'D:\SQLData\TestDB.mdf' -Size 102400 -IsPrimaryFile -AsSpec + $primaryFileGroup = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($primaryFile) -IsDefault -AsSpec - # Create secondary filegroup - $secondaryFile = New-SqlDscDataFile -Name 'TestDB_Secondary' -FileName 'E:\SQLData\TestDB.ndf' -Size 204800 -AsSpec - $secondaryFileGroup = New-SqlDscFileGroup -Name 'SECONDARY' -Files @($secondaryFile) -AsSpec + # Create secondary filegroup + $secondaryFile = New-SqlDscDataFile -Name 'TestDB_Secondary' -FileName 'E:\SQLData\TestDB.ndf' -Size 204800 -AsSpec + $secondaryFileGroup = New-SqlDscFileGroup -Name 'SECONDARY' -Files @($secondaryFile) -AsSpec - $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestDB' -FileGroup @($primaryFileGroup, $secondaryFileGroup) -Force - - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'TestDB' - } + $result = New-SqlDscDatabase -ServerObject $mockServerObject -Name 'TestDB' -FileGroup @($primaryFileGroup, $secondaryFileGroup) -Force + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestDB' Should -Invoke -CommandName 'New-SqlDscFileGroup' -Exactly -Times 2 -Scope It Should -Invoke -CommandName 'Add-SqlDscFileGroup' -Exactly -Times 2 -Scope It } From 512a16f66bb7e71e5545dea5dbff2770f64629d8 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:17:35 +0100 Subject: [PATCH 56/87] Enhance documentation for ConvertTo-SqlDscDataFile by adding INPUTS section and clarifying OUTPUTS description --- source/Public/ConvertTo-SqlDscDataFile.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/source/Public/ConvertTo-SqlDscDataFile.ps1 b/source/Public/ConvertTo-SqlDscDataFile.ps1 index 46f0ebbfed..b2a81e473c 100644 --- a/source/Public/ConvertTo-SqlDscDataFile.ps1 +++ b/source/Public/ConvertTo-SqlDscDataFile.ps1 @@ -13,12 +13,19 @@ .PARAMETER DataFileSpec The DatabaseFileSpec object containing the data file configuration. + .INPUTS + None + + This command does not accept pipeline input. + .OUTPUTS Microsoft.SqlServer.Management.Smo.DataFile + Returns a SMO DataFile object bound to the provided FileGroup. + .EXAMPLE $fileSpec = New-SqlDscDataFile -Name 'TestDB_Data' -FileName 'C:\SQLData\TestDB.mdf' -AsSpec - $fileGroup = New-Object Microsoft.SqlServer.Management.Smo.FileGroup($database, 'PRIMARY') + $fileGroup = [Microsoft.SqlServer.Management.Smo.FileGroup]::new($database, 'PRIMARY') $dataFile = ConvertTo-SqlDscDataFile -FileGroupObject $fileGroup -DataFileSpec $fileSpec Converts a DatabaseFileSpec to a SMO DataFile object. From 8be826ed42409725496611ef9f0e9705b0aadf80 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:19:38 +0100 Subject: [PATCH 57/87] Add Force parameter to New-SqlDscDataFile tests to bypass confirmation prompts --- .../New-SqlDscDataFile.Integration.Tests.ps1 | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 index 299d0337b1..be4291f657 100644 --- a/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDataFile.Integration.Tests.ps1 @@ -56,13 +56,13 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 $script:mockDatabase.Name = 'TestDatabase' $script:mockDatabase.Parent = $script:serverObject - $script:mockFileGroup = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' -Confirm:$false + $script:mockFileGroup = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' -Force -ErrorAction 'Stop' } It 'Should create a DataFile and add it to FileGroup without PassThru' { $initialFileCount = $script:mockFileGroup.Files.Count - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Confirm:$false + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -Force -ErrorAction 'Stop' $result | Should -BeNullOrEmpty $script:mockFileGroup.Files.Count | Should -Be ($initialFileCount + 1) @@ -75,7 +75,7 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 It 'Should create a DataFile and return it with PassThru' { $initialFileCount = $script:mockFileGroup.Files.Count - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile2' -FileName 'C:\Data\TestDataFile2.ndf' -PassThru -Confirm:$false + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile2' -FileName 'C:\Data\TestDataFile2.ndf' -PassThru -Force -ErrorAction 'Stop' $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' @@ -88,7 +88,7 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 It 'Should support Force parameter to bypass confirmation' { $initialFileCount = $script:mockFileGroup.Files.Count - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'ForcedDataFile' -FileName 'C:\Data\ForcedDataFile.ndf' -PassThru -Force + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'ForcedDataFile' -FileName 'C:\Data\ForcedDataFile.ndf' -PassThru -Force -ErrorAction 'Stop' $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' @@ -100,7 +100,7 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 It 'Should not add file when WhatIf is specified' { $initialFileCount = $script:mockFileGroup.Files.Count - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'DeclinedDataFile' -FileName 'C:\Data\DeclinedDataFile.ndf' -Confirm:$false -WhatIf + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'DeclinedDataFile' -FileName 'C:\Data\DeclinedDataFile.ndf' -Force -ErrorAction 'Stop' -WhatIf $result | Should -BeNullOrEmpty $script:mockFileGroup.Files.Count | Should -Be $initialFileCount @@ -114,25 +114,25 @@ Describe 'New-SqlDscDataFile' -Tag @('Integration_SQL2017', 'Integration_SQL2019 $script:mockDatabase.Name = 'TestDatabase' $script:mockDatabase.Parent = $script:serverObject - $script:mockFileGroup = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' -Confirm:$false + $script:mockFileGroup = New-SqlDscFileGroup -Database $script:mockDatabase -Name 'TestFileGroup' -Force -ErrorAction 'Stop' } It 'Should allow setting Size property on returned DataFile' { - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -PassThru -Confirm:$false + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -PassThru -Force -ErrorAction 'Stop' $result.Size = 1024.0 $result.Size | Should -Be 1024.0 } It 'Should allow setting Growth property on returned DataFile' { - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -PassThru -Confirm:$false + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -PassThru -Force -ErrorAction 'Stop' $result.Growth = 64.0 $result.Growth | Should -Be 64.0 } It 'Should allow setting GrowthType property on returned DataFile' { - $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -PassThru -Confirm:$false + $result = New-SqlDscDataFile -FileGroup $script:mockFileGroup -Name 'TestDataFile' -FileName 'C:\Data\TestDataFile.ndf' -PassThru -Force -ErrorAction 'Stop' $result.GrowthType = [Microsoft.SqlServer.Management.Smo.FileGrowthType]::Percent $result.GrowthType | Should -Be 'Percent' From d1ffdc0c4ad2eb5c209ed47d768a3491fd34bec8 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:20:36 +0100 Subject: [PATCH 58/87] Add INPUTS section to Add-SqlDscFileGroup documentation for clarity --- source/Public/Add-SqlDscFileGroup.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/Public/Add-SqlDscFileGroup.ps1 b/source/Public/Add-SqlDscFileGroup.ps1 index e8b08326b1..c5fae30b50 100644 --- a/source/Public/Add-SqlDscFileGroup.ps1 +++ b/source/Public/Add-SqlDscFileGroup.ps1 @@ -20,6 +20,11 @@ .PARAMETER Force Specifies that the FileGroup should be added without confirmation. + .INPUTS + [Microsoft.SqlServer.Management.Smo.FileGroup] + + FileGroup objects that will be added to the Database. + .OUTPUTS None, or [Microsoft.SqlServer.Management.Smo.FileGroup[]] if PassThru is specified. From 24ec742392be856bd10cc8534966951e98ef6874 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:23:09 +0100 Subject: [PATCH 59/87] Refactor New-SqlDscDatabaseSnapshot test to improve mock DatabaseFileGroupSpec creation and streamline assertions --- .../New-SqlDscDatabaseSnapshot.Tests.ps1 | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 b/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 index 384edfdc45..90d881f65f 100644 --- a/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 @@ -107,26 +107,24 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { } It 'Should pass FileGroup parameter when specified' { - InModuleScope -Parameters @{ - mockServerObject = $mockServerObject - } -ScriptBlock { - # Create a mock DatabaseFileGroupSpec + # Create a mock DatabaseFileGroupSpec using InModuleScope to access internal classes + $mockFileGroupSpec = InModuleScope -ScriptBlock { $mockDataFileSpec = [DatabaseFileSpec]@{ Name = 'TestData' FileName = 'C:\Snapshots\TestData.ss' } - $mockFileGroupSpec = [DatabaseFileGroupSpec]@{ + [DatabaseFileGroupSpec]@{ Name = 'PRIMARY' Files = @($mockDataFileSpec) } + } - $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -FileGroup @($mockFileGroupSpec) -Force + $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -FileGroup @($mockFileGroupSpec) -Force - Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { - $FileGroup -and $FileGroup.Count -eq 1 -and $FileGroup[0].Name -eq 'PRIMARY' - } -Exactly -Times 1 - } + Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { + $FileGroup -and $FileGroup.Count -eq 1 -and $FileGroup[0].Name -eq 'PRIMARY' + } -Exactly -Times 1 } } From 72431a757db4810cf55212b68793ef2523d77518 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:24:41 +0100 Subject: [PATCH 60/87] Add ErrorAction 'Stop' to Add-SqlDscFileGroup tests for improved error handling --- .../Add-SqlDscFileGroup.Integration.Tests.ps1 | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 index f87fd13993..5b18b2fbc7 100644 --- a/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Add-SqlDscFileGroup.Integration.Tests.ps1 @@ -53,20 +53,20 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 $script:testDatabase.Name = 'TestDatabase' $script:testDatabase.Parent = $script:serverObject - $script:testFileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'TestFileGroup' -Confirm:$false + $script:testFileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'TestFileGroup' -Confirm:$false -ErrorAction 'Stop' } It 'Should add a FileGroup to Database successfully' { $initialCount = $script:testDatabase.FileGroups.Count - Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $script:testFileGroup + Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $script:testFileGroup -ErrorAction 'Stop' $script:testDatabase.FileGroups.Count | Should -Be ($initialCount + 1) $script:testDatabase.FileGroups[$script:testFileGroup.Name] | Should -Be $script:testFileGroup } It 'Should return FileGroup when using PassThru' { - $result = Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $script:testFileGroup -PassThru + $result = Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $script:testFileGroup -PassThru -ErrorAction 'Stop' $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' @@ -76,18 +76,18 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 It 'Should accept FileGroup from pipeline' { $initialCount = $script:testDatabase.FileGroups.Count - $script:testFileGroup | Add-SqlDscFileGroup -Database $script:testDatabase + $script:testFileGroup | Add-SqlDscFileGroup -Database $script:testDatabase -ErrorAction 'Stop' $script:testDatabase.FileGroups.Count | Should -Be ($initialCount + 1) } It 'Should add multiple FileGroups to Database' { - $fileGroup1 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup1' -Confirm:$false - $fileGroup2 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup2' -Confirm:$false + $fileGroup1 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup1' -Confirm:$false -ErrorAction 'Stop' + $fileGroup2 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup2' -Confirm:$false -ErrorAction 'Stop' $initialCount = $script:testDatabase.FileGroups.Count - Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup @($fileGroup1, $fileGroup2) + Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup @($fileGroup1, $fileGroup2) -ErrorAction 'Stop' $script:testDatabase.FileGroups.Count | Should -Be ($initialCount + 2) $script:testDatabase.FileGroups[$fileGroup1.Name] | Should -Be $fileGroup1 @@ -95,10 +95,10 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 } It 'Should add multiple FileGroups via pipeline and return them with PassThru' { - $fileGroup1 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup1' -Confirm:$false - $fileGroup2 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup2' -Confirm:$false + $fileGroup1 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup1' -Confirm:$false -ErrorAction 'Stop' + $fileGroup2 = New-SqlDscFileGroup -Database $script:testDatabase -Name 'FileGroup2' -Confirm:$false -ErrorAction 'Stop' - $result = @($fileGroup1, $fileGroup2) | Add-SqlDscFileGroup -Database $script:testDatabase -PassThru + $result = @($fileGroup1, $fileGroup2) | Add-SqlDscFileGroup -Database $script:testDatabase -PassThru -ErrorAction 'Stop' $result | Should -HaveCount 2 $result[0] | Should -Be $fileGroup1 @@ -112,13 +112,13 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 $script:testDatabase.Name = 'TestDatabase' $script:testDatabase.Parent = $script:serverObject - $script:testFileGroup = New-SqlDscFileGroup -Name 'TestFileGroup' + $script:testFileGroup = New-SqlDscFileGroup -Name 'TestFileGroup' -ErrorAction 'Stop' } It 'Should update FileGroup parent reference when added to Database' { $script:testFileGroup.Parent | Should -BeNullOrEmpty - Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $script:testFileGroup + Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $script:testFileGroup -ErrorAction 'Stop' # Note: The parent may or may not be updated depending on SMO implementation # This test verifies the FileGroup is in the collection @@ -135,13 +135,13 @@ Describe 'Add-SqlDscFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL201 It 'Should create a complete FileGroup with DataFile structure' { # Create FileGroup - $fileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'SecondaryFileGroup' -Confirm:$false + $fileGroup = New-SqlDscFileGroup -Database $script:testDatabase -Name 'SecondaryFileGroup' -Confirm:$false -ErrorAction 'Stop' # Create DataFile - it will be automatically added to the FileGroup - $null = New-SqlDscDataFile -FileGroup $fileGroup -Name 'SecondaryDataFile' -FileName 'C:\Data\SecondaryDataFile.ndf' -Confirm:$false + $null = New-SqlDscDataFile -FileGroup $fileGroup -Name 'SecondaryDataFile' -FileName 'C:\Data\SecondaryDataFile.ndf' -Confirm:$false -ErrorAction 'Stop' # Add FileGroup to Database - Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $fileGroup + Add-SqlDscFileGroup -Database $script:testDatabase -FileGroup $fileGroup -ErrorAction 'Stop' # Verify structure $script:testDatabase.FileGroups[$fileGroup.Name] | Should -Be $fileGroup From 8885a76aa962ac2baff016ec2aea277bd47e3b44 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:29:56 +0100 Subject: [PATCH 61/87] Refactor New-SqlDscDataFile tests to streamline assertions and improve readability --- .../Unit/Public/New-SqlDscDataFile.Tests.ps1 | 302 +++++++++--------- 1 file changed, 147 insertions(+), 155 deletions(-) diff --git a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 index 460b625cc6..0a31233995 100644 --- a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 @@ -59,182 +59,192 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { } It 'Should create a DataFile and add it to the FileGroup' { - InModuleScope -Parameters @{ - mockFileGroupObject = $mockFileGroupObject - } -ScriptBlock { - param ($mockFileGroupObject) + $initialFileCount = $mockFileGroupObject.Files.Count - $initialFileCount = $mockFileGroupObject.Files.Count + $null = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MyDataFile' -FileName 'C:\Data\MyDataFile.mdf' -Confirm:$false - New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MyDataFile' -FileName 'C:\Data\MyDataFile.mdf' -Confirm:$false - - $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) - $addedFile = $mockFileGroupObject.Files | Where-Object -FilterScript { $_.Name -eq 'MyDataFile' } - $addedFile | Should -Not -BeNullOrEmpty - $addedFile.FileName | Should -Be 'C:\Data\MyDataFile.mdf' - } + $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) + $addedFile = $mockFileGroupObject.Files | Where-Object -FilterScript { $_.Name -eq 'MyDataFile' } + $addedFile | Should -Not -BeNullOrEmpty + $addedFile.FileName | Should -Be 'C:\Data\MyDataFile.mdf' } It 'Should return the created DataFile when PassThru is specified' { - InModuleScope -Parameters @{ - mockFileGroupObject = $mockFileGroupObject - } -ScriptBlock { - param ($mockFileGroupObject) - - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'PassThruDataFile' -FileName 'C:\Data\PassThruDataFile.mdf' -PassThru -Confirm:$false - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $result.Name | Should -Be 'PassThruDataFile' - $result.FileName | Should -Be 'C:\Data\PassThruDataFile.mdf' - $result.Parent | Should -Be $mockFileGroupObject - } + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'PassThruDataFile' -FileName 'C:\Data\PassThruDataFile.mdf' -PassThru -Confirm:$false + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $result.Name | Should -Be 'PassThruDataFile' + $result.FileName | Should -Be 'C:\Data\PassThruDataFile.mdf' + $result.Parent | Should -Be $mockFileGroupObject } It 'Should not return anything when PassThru is not specified' { - InModuleScope -Parameters @{ - mockFileGroupObject = $mockFileGroupObject - } -ScriptBlock { - param ($mockFileGroupObject) + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'NoPassThruDataFile' -FileName 'C:\Data\NoPassThruDataFile.mdf' -Confirm:$false - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'NoPassThruDataFile' -FileName 'C:\Data\NoPassThruDataFile.mdf' -Confirm:$false - - $result | Should -BeNullOrEmpty - } + $result | Should -BeNullOrEmpty } It 'Should create a sparse file for database snapshot' { - InModuleScope -Parameters @{ - mockFileGroupObject = $mockFileGroupObject - } -ScriptBlock { - param ($mockFileGroupObject) + $initialFileCount = $mockFileGroupObject.Files.Count - $initialFileCount = $mockFileGroupObject.Files.Count + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MySnapshot_Data' -FileName 'C:\Snapshots\MySnapshot_Data.ss' -PassThru -Confirm:$false - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'MySnapshot_Data' -FileName 'C:\Snapshots\MySnapshot_Data.ss' -PassThru -Confirm:$false - - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'MySnapshot_Data' - $result.FileName | Should -Be 'C:\Snapshots\MySnapshot_Data.ss' - $result.Parent | Should -Be $mockFileGroupObject - $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) - } + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'MySnapshot_Data' + $result.FileName | Should -Be 'C:\Snapshots\MySnapshot_Data.ss' + $result.Parent | Should -Be $mockFileGroupObject + $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) } It 'Should support Force parameter to bypass confirmation' { - InModuleScope -Parameters @{ - mockFileGroupObject = $mockFileGroupObject - } -ScriptBlock { - param ($mockFileGroupObject) + $initialFileCount = $mockFileGroupObject.Files.Count - $initialFileCount = $mockFileGroupObject.Files.Count + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'ForcedDataFile' -FileName 'C:\Data\ForcedDataFile.mdf' -PassThru -Force - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'ForcedDataFile' -FileName 'C:\Data\ForcedDataFile.mdf' -PassThru -Force - - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'ForcedDataFile' - $result.FileName | Should -Be 'C:\Data\ForcedDataFile.mdf' - $result.Parent | Should -Be $mockFileGroupObject - $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) - } + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'ForcedDataFile' + $result.FileName | Should -Be 'C:\Data\ForcedDataFile.mdf' + $result.Parent | Should -Be $mockFileGroupObject + $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) } It 'Should not add file when WhatIf is specified' { - InModuleScope -Parameters @{ - mockFileGroupObject = $mockFileGroupObject - } -ScriptBlock { - param ($mockFileGroupObject) + $initialFileCount = $mockFileGroupObject.Files.Count - $initialFileCount = $mockFileGroupObject.Files.Count + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'WhatIfDataFile' -FileName 'C:\Data\WhatIfDataFile.mdf' -WhatIf - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -Name 'WhatIfDataFile' -FileName 'C:\Data\WhatIfDataFile.mdf' -WhatIf - - $result | Should -BeNullOrEmpty - $mockFileGroupObject.Files.Count | Should -Be $initialFileCount - } + $result | Should -BeNullOrEmpty + $mockFileGroupObject.Files.Count | Should -Be $initialFileCount } } Context 'Parameter validation' { - BeforeAll { - $commandInfo = Get-Command -Name 'New-SqlDscDataFile' + It 'Should have the correct parameters in parameter set Standard' -ForEach @( + @{ + ExpectedParameterSetName = 'Standard' + ExpectedParameters = '-FileGroup -Name -FileName [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'New-SqlDscDataFile').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters } - It 'Should have three parameter sets (Standard, FromSpec, AsSpec)' { - $commandInfo.ParameterSets.Count | Should -Be 3 - $commandInfo.ParameterSets.Name | Should -Contain 'Standard' - $commandInfo.ParameterSets.Name | Should -Contain 'FromSpec' - $commandInfo.ParameterSets.Name | Should -Contain 'AsSpec' + It 'Should have the correct parameters in parameter set FromSpec' -ForEach @( + @{ + ExpectedParameterSetName = 'FromSpec' + ExpectedParameters = '-FileGroup -DataFileSpec [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'New-SqlDscDataFile').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters } - It 'Should have FileGroup as a mandatory parameter' { - $parameterInfo = $commandInfo.Parameters['FileGroup'] - $parameterInfo.Attributes.Mandatory | Should -Contain $true + It 'Should have the correct parameters in parameter set AsSpec' -ForEach @( + @{ + ExpectedParameterSetName = 'AsSpec' + ExpectedParameters = '-Name -FileName -AsSpec [-Size ] [-MaxSize ] [-Growth ] [-GrowthType ] [-IsPrimaryFile] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'New-SqlDscDataFile').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters } - It 'Should have Name as a mandatory parameter' { - $parameterInfo = $commandInfo.Parameters['Name'] - $parameterInfo.Attributes.Mandatory | Should -Contain $true + It 'Should have FileGroup as a mandatory parameter in Standard parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDataFile').Parameters['FileGroup'] + $standardSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'Standard' } + $standardSetAttribute.Mandatory | Should -BeTrue } - It 'Should have FileName as a mandatory parameter' { - $parameterInfo = $commandInfo.Parameters['FileName'] - $parameterInfo.Attributes.Mandatory | Should -Contain $true + It 'Should have FileGroup as a mandatory parameter in FromSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDataFile').Parameters['FileGroup'] + $fromSpecSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'FromSpec' } + $fromSpecSetAttribute.Mandatory | Should -BeTrue } - It 'Should have PassThru parameter' { - $parameterInfo = $commandInfo.Parameters['PassThru'] - $parameterInfo | Should -Not -BeNullOrEmpty - $parameterInfo.ParameterType.Name | Should -Be 'SwitchParameter' + It 'Should have Name as a mandatory parameter in Standard parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDataFile').Parameters['Name'] + $standardSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'Standard' } + $standardSetAttribute.Mandatory | Should -BeTrue } - It 'Should support ShouldProcess' { - $commandInfo.Parameters.ContainsKey('WhatIf') | Should -BeTrue - $commandInfo.Parameters.ContainsKey('Confirm') | Should -BeTrue + It 'Should have Name as a mandatory parameter in AsSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDataFile').Parameters['Name'] + $asSpecSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'AsSpec' } + $asSpecSetAttribute.Mandatory | Should -BeTrue } - It 'Should have Force parameter' { - $parameterInfo = $commandInfo.Parameters['Force'] - $parameterInfo | Should -Not -BeNullOrEmpty + It 'Should have FileName as a mandatory parameter in Standard parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDataFile').Parameters['FileName'] + $standardSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'Standard' } + $standardSetAttribute.Mandatory | Should -BeTrue } - It 'Should have ConfirmImpact set to High' { - $commandInfo.ScriptBlock.Attributes | Where-Object { $_.TypeId.Name -eq 'CmdletBindingAttribute' } | - ForEach-Object { $_.ConfirmImpact } | Should -Be 'High' + It 'Should have FileName as a mandatory parameter in AsSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDataFile').Parameters['FileName'] + $asSpecSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'AsSpec' } + $asSpecSetAttribute.Mandatory | Should -BeTrue + } + + It 'Should have DataFileSpec as a mandatory parameter in FromSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDataFile').Parameters['DataFileSpec'] + $fromSpecSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'FromSpec' } + $fromSpecSetAttribute.Mandatory | Should -BeTrue + } + + It 'Should have AsSpec as a mandatory parameter in AsSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDataFile').Parameters['AsSpec'] + $asSpecSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'AsSpec' } + $asSpecSetAttribute.Mandatory | Should -BeTrue } } Context 'When creating a DataFile with AsSpec parameter set' { It 'Should return a DatabaseFileSpec object' { - InModuleScope -ScriptBlock { - $result = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -AsSpec + $result = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -AsSpec - $result | Should -Not -BeNullOrEmpty - $result.GetType().Name | Should -Be 'DatabaseFileSpec' - $result.Name | Should -Be 'MyDB_Primary' - $result.FileName | Should -Be 'D:\SQLData\MyDB.mdf' - } + $result | Should -Not -BeNullOrEmpty + $result.GetType().Name | Should -Be 'DatabaseFileSpec' + $result.Name | Should -Be 'MyDB_Primary' + $result.FileName | Should -Be 'D:\SQLData\MyDB.mdf' } It 'Should set IsPrimaryFile when specified' { - InModuleScope -ScriptBlock { - $result = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -IsPrimaryFile -AsSpec + $result = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -IsPrimaryFile -AsSpec - $result | Should -Not -BeNullOrEmpty - $result.IsPrimaryFile | Should -BeTrue - } + $result | Should -Not -BeNullOrEmpty + $result.IsPrimaryFile | Should -BeTrue } It 'Should set Size, MaxSize, Growth, and GrowthType when specified' { - InModuleScope -ScriptBlock { - $result = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -Size 102400 -MaxSize 5242880 -Growth 10240 -GrowthType 'KB' -AsSpec - - $result | Should -Not -BeNullOrEmpty - $result.Size | Should -Be 102400 - $result.MaxSize | Should -Be 5242880 - $result.Growth | Should -Be 10240 - $result.GrowthType | Should -Be 'KB' - } + $result = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -Size 102400 -MaxSize 5242880 -Growth 10240 -GrowthType 'KB' -AsSpec + + $result | Should -Not -BeNullOrEmpty + $result.Size | Should -Be 102400 + $result.MaxSize | Should -Be 5242880 + $result.Growth | Should -Be 10240 + $result.GrowthType | Should -Be 'KB' } } @@ -245,58 +255,40 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { } It 'Should create a DataFile from a DatabaseFileSpec in the PRIMARY filegroup' { - InModuleScope -Parameters @{ - mockDatabaseObject = $mockDatabaseObject - } -ScriptBlock { - param ($mockDatabaseObject) - - $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'PRIMARY' + $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'PRIMARY' - $fileSpec = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -IsPrimaryFile -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -IsPrimaryFile -AsSpec - $initialFileCount = $mockFileGroupObject.Files.Count + $initialFileCount = $mockFileGroupObject.Files.Count - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFileSpec $fileSpec -PassThru -Force + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFileSpec $fileSpec -PassThru -Force - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) - } + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) } It 'Should throw an error when IsPrimaryFile is specified but filegroup is not PRIMARY' { - InModuleScope -Parameters @{ - mockDatabaseObject = $mockDatabaseObject - } -ScriptBlock { - param ($mockDatabaseObject) - - $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'SECONDARY' + $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'SECONDARY' - $fileSpec = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -IsPrimaryFile -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'MyDB_Primary' -FileName 'D:\SQLData\MyDB.mdf' -IsPrimaryFile -AsSpec - { New-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFileSpec $fileSpec -Force -ErrorAction Stop } | - Should -Throw -ExpectedMessage '*The primary file must reside in the PRIMARY filegroup*' - } + { New-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFileSpec $fileSpec -Force -ErrorAction Stop } | + Should -Throw -ExpectedMessage '*The primary file must reside in the PRIMARY filegroup*' } It 'Should create a DataFile from a DatabaseFileSpec without IsPrimaryFile in a non-PRIMARY filegroup' { - InModuleScope -Parameters @{ - mockDatabaseObject = $mockDatabaseObject - } -ScriptBlock { - param ($mockDatabaseObject) - - $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'SECONDARY' + $mockFileGroupObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $mockDatabaseObject, 'SECONDARY' - $fileSpec = New-SqlDscDataFile -Name 'MyDB_Secondary' -FileName 'D:\SQLData\MyDB_Secondary.ndf' -AsSpec + $fileSpec = New-SqlDscDataFile -Name 'MyDB_Secondary' -FileName 'D:\SQLData\MyDB_Secondary.ndf' -AsSpec - $initialFileCount = $mockFileGroupObject.Files.Count + $initialFileCount = $mockFileGroupObject.Files.Count - $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFileSpec $fileSpec -PassThru -Force + $result = New-SqlDscDataFile -FileGroup $mockFileGroupObject -DataFileSpec $fileSpec -PassThru -Force - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' - $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) - } + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.DataFile' + $mockFileGroupObject.Files.Count | Should -Be ($initialFileCount + 1) } } } From 0cabd5fe6983d3a69ca8a00622ae67f60ff903f9 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:30:46 +0100 Subject: [PATCH 62/87] Remove verbose message for file group creation in New-SqlDscFileGroup function --- source/Public/New-SqlDscFileGroup.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index cb3a2eb01d..ee191463b4 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -172,8 +172,6 @@ function New-SqlDscFileGroup { if ($PSCmdlet.ParameterSetName -eq 'WithDatabaseFromSpec') { - Write-Verbose -Message ('Creating file group: {0}' -f $FileGroupSpec.Name) - # Convert the spec object to SMO FileGroup $fileGroupObject = ConvertTo-SqlDscFileGroup -DatabaseObject $Database -FileGroupSpec $FileGroupSpec } From 20cf372e37ecb354daa338d23798d3dee14afd9b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:32:21 +0100 Subject: [PATCH 63/87] Enhance documentation for New-SqlDscDataFile parameters and outputs, clarifying behavior of -AsSpec and -PassThru options --- source/Public/New-SqlDscDataFile.ps1 | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index 029fd10d53..2d257e1cd4 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -30,6 +30,10 @@ or passed directly to New-SqlDscDatabase to define data files before the database is created. + When this parameter is used, the command always returns a DatabaseFileSpec + object, regardless of the PassThru parameter, and the FileGroup parameter + is not available. + .PARAMETER Size Specifies the initial size of the data file in kilobytes. Only valid when used with the -AsSpec parameter to create a DatabaseFileSpec object. @@ -57,6 +61,8 @@ .PARAMETER PassThru Returns the DataFile object that was created and added to the FileGroup. + Only available when using the Standard or FromSpec parameter sets. When + using the AsSpec parameter set, a DatabaseFileSpec object is always returned. .PARAMETER Force Specifies that the DataFile object should be created without prompting for @@ -99,7 +105,9 @@ Creates a DatabaseFileSpec object with all properties set directly via parameters. .OUTPUTS - None. Unless -PassThru is specified, in which case it returns `[Microsoft.SqlServer.Management.Smo.DataFile]`. + None. Unless -PassThru is specified for the Standard or FromSpec parameter + sets, in which case it returns `[Microsoft.SqlServer.Management.Smo.DataFile]`. + When using the -AsSpec parameter, always returns `[DatabaseFileSpec]`. #> function New-SqlDscDataFile { @@ -239,8 +247,6 @@ function New-SqlDscDataFile { if ($PSCmdlet.ParameterSetName -eq 'FromSpec') { - Write-Verbose -Message (' Creating data file: {0}' -f $DataFileSpec.Name) - # Convert the spec object to SMO DataFile $dataFileObject = ConvertTo-SqlDscDataFile -FileGroupObject $FileGroup -DataFileSpec $DataFileSpec } From c0b239eb559ec1af878b9efc4a865b7a620c5249 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:38:49 +0100 Subject: [PATCH 64/87] Remove PassThru parameter from New-SqlDscFileGroup function to streamline functionality --- source/Public/New-SqlDscFileGroup.ps1 | 9 --------- 1 file changed, 9 deletions(-) diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index ee191463b4..7de29d4be1 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -35,9 +35,6 @@ .PARAMETER IsDefault Specifies whether this file group should be the default file group. Only valid when using -AsSpec. - .PARAMETER PassThru - Returns the created FileGroup object. By default, this cmdlet does not generate any output. - .PARAMETER Force Specifies that the FileGroup object should be created without prompting for confirmation. By default, the command prompts for confirmation when the Database @@ -117,12 +114,6 @@ function New-SqlDscFileGroup [System.Management.Automation.SwitchParameter] $IsDefault, - [Parameter(ParameterSetName = 'AsSpec')] - [Parameter(ParameterSetName = 'WithDatabase')] - [Parameter(ParameterSetName = 'WithDatabaseFromSpec')] - [System.Management.Automation.SwitchParameter] - $PassThru, - [Parameter(ParameterSetName = 'WithDatabase')] [Parameter(ParameterSetName = 'WithDatabaseFromSpec')] [System.Management.Automation.SwitchParameter] From 0d770811eb2e0d716bba14d7debe8c8c2492e698 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:43:00 +0100 Subject: [PATCH 65/87] Enhance DatabaseFileGroupSpec tests to verify instance creation with default constructor, Name only, and Name with Files array --- .../Classes/DatabaseFileGroupSpec.Tests.ps1 | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 b/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 index 08bf552921..4d7fbe78be 100644 --- a/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 +++ b/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 @@ -26,6 +26,8 @@ BeforeDiscovery { BeforeAll { $script:dscModuleName = 'SqlServerDsc' + $env:SqlServerDscCI = $true + $script:moduleUnderTest = Import-Module -Name $script:dscModuleName -PassThru -Force -ErrorAction 'Stop' $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName @@ -34,31 +36,47 @@ BeforeAll { AfterAll { $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + Remove-Item -Path 'env:SqlServerDscCI' + # Unload the module being tested so that it doesn't impact any other tests. Get-Module -Name $script:dscModuleName -All | Remove-Module -Force } Describe 'DatabaseFileGroupSpec' -Tag 'DatabaseFileGroupSpec' { Context 'When instantiating the class' { - It 'Should not throw when instantiated with default constructor' { - InModuleScope -ScriptBlock { - { [DatabaseFileGroupSpec]::new() } | Should -Not -Throw + It 'Should create an instance with default constructor' { + $script:instance = InModuleScope -ScriptBlock { + [DatabaseFileGroupSpec]::new() } + + $instance | Should -Not -BeNullOrEmpty + $instance.GetType().Name | Should -Be 'DatabaseFileGroupSpec' } - It 'Should not throw when instantiated with Name only' { - InModuleScope -ScriptBlock { - { [DatabaseFileGroupSpec]::new('PRIMARY') } | Should -Not -Throw + It 'Should create an instance with Name only' { + $script:instance = InModuleScope -ScriptBlock { + [DatabaseFileGroupSpec]::new('PRIMARY') } + + $instance | Should -Not -BeNullOrEmpty + $instance.GetType().Name | Should -Be 'DatabaseFileGroupSpec' + $instance.Name | Should -Be 'PRIMARY' } - It 'Should not throw when instantiated with Name and Files array' { - InModuleScope -ScriptBlock { + It 'Should create an instance with Name and Files array' { + $script:instance = InModuleScope -ScriptBlock { $files = @( [DatabaseFileSpec]::new('File1', 'C:\Data\File1.mdf') ) - { [DatabaseFileGroupSpec]::new('PRIMARY', $files) } | Should -Not -Throw + [DatabaseFileGroupSpec]::new('PRIMARY', $files) } + + $instance | Should -Not -BeNullOrEmpty + $instance.GetType().Name | Should -Be 'DatabaseFileGroupSpec' + $instance.Name | Should -Be 'PRIMARY' + $instance.Files | Should -HaveCount 1 + $instance.Files[0].Name | Should -Be 'File1' + $instance.Files[0].FileName | Should -Be 'C:\Data\File1.mdf' } } From fc7884588bb7e138f212a4f7be7828ee0f138a82 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:45:30 +0100 Subject: [PATCH 66/87] Refactor BeforeAll and AfterAll blocks to streamline module import and environment variable management --- tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 b/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 index 58e11e63f7..393feda938 100644 --- a/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 +++ b/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 @@ -26,13 +26,21 @@ BeforeDiscovery { BeforeAll { $script:dscModuleName = 'SqlServerDsc' - $script:moduleUnderTest = Import-Module -Name $script:dscModuleName -PassThru -Force -ErrorAction 'Stop' + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + $env:SqlServerDscCI = $true $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName } AfterAll { $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'env:SqlServerDscCI' # Unload the module being tested so that it doesn't impact any other tests. Get-Module -Name $script:dscModuleName -All | Remove-Module -Force From a64e7366259a3c49844e4c885fbb77dda9c127c7 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:46:55 +0100 Subject: [PATCH 67/87] Refactor DatabaseFileSpec tests to simplify instantiation checks by removing unnecessary script blocks --- tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 b/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 index 393feda938..861f2c34c6 100644 --- a/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 +++ b/tests/Unit/Classes/DatabaseFileSpec.Tests.ps1 @@ -50,13 +50,13 @@ Describe 'DatabaseFileSpec' -Tag 'DatabaseFileSpec' { Context 'When instantiating the class' { It 'Should not throw when instantiated with default constructor' { InModuleScope -ScriptBlock { - { [DatabaseFileSpec]::new() } | Should -Not -Throw + [DatabaseFileSpec]::new() } } It 'Should not throw when instantiated with Name and FileName' { InModuleScope -ScriptBlock { - { [DatabaseFileSpec]::new('TestFile', 'C:\Data\TestFile.mdf') } | Should -Not -Throw + [DatabaseFileSpec]::new('TestFile', 'C:\Data\TestFile.mdf') } } } From 2bd3c56ce9620ac12fe38047252096ab2e898122 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:48:26 +0100 Subject: [PATCH 68/87] Fix comments and error messages in Add-SqlDscFileGroup tests for clarity --- tests/Unit/Public/Add-SqlDscFileGroup.Tests.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Public/Add-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/Add-SqlDscFileGroup.Tests.ps1 index 59054f22e2..5d4a6ce174 100644 --- a/tests/Unit/Public/Add-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/Add-SqlDscFileGroup.Tests.ps1 @@ -14,20 +14,20 @@ BeforeDiscovery { { if (-not (Get-Module -Name 'DscResource.Test')) { - # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } - # If the dependencies has not been resolved, this will throw an error. + # If the dependencies have not been resolved, this will throw an error. Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' } } catch [System.IO.FileNotFoundException] { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' } } From 349b32805f203b17e75d951fc33bff8cb5508261 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:50:39 +0100 Subject: [PATCH 69/87] Refactor New-SqlDscFileGroup tests to eliminate InModuleScope usage for improved readability and maintainability --- .../Unit/Public/New-SqlDscFileGroup.Tests.ps1 | 86 ++++++------------- 1 file changed, 28 insertions(+), 58 deletions(-) diff --git a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 index e3797e6eb9..34900ab62a 100644 --- a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 @@ -61,91 +61,61 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { } It 'Should create a FileGroup successfully' { - InModuleScope -Parameters @{ - mockDatabaseObject = $mockDatabaseObject - } -ScriptBlock { - param ($mockDatabaseObject) + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'MyFileGroup' -Confirm:$false - $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'MyFileGroup' -Confirm:$false - - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' - $result.Name | Should -Be 'MyFileGroup' - $result.Parent | Should -Be $mockDatabaseObject - } + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'MyFileGroup' + $result.Parent | Should -Be $mockDatabaseObject } It 'Should create a PRIMARY FileGroup successfully' { - InModuleScope -Parameters @{ - mockDatabaseObject = $mockDatabaseObject - } -ScriptBlock { - param ($mockDatabaseObject) - - $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'PRIMARY' -Confirm:$false + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'PRIMARY' -Confirm:$false - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'PRIMARY' - $result.Parent | Should -Be $mockDatabaseObject - } + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'PRIMARY' + $result.Parent | Should -Be $mockDatabaseObject } It 'Should support Force parameter to bypass confirmation' { - InModuleScope -Parameters @{ - mockDatabaseObject = $mockDatabaseObject - } -ScriptBlock { - param ($mockDatabaseObject) - - $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'ForcedFileGroup' -Force + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'ForcedFileGroup' -Force - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'ForcedFileGroup' - $result.Parent | Should -Be $mockDatabaseObject - } + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'ForcedFileGroup' + $result.Parent | Should -Be $mockDatabaseObject } It 'Should throw terminating error when Database object has no Parent property set' { - InModuleScope -ScriptBlock { - $mockDatabaseWithoutParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' - $mockDatabaseWithoutParent.Name = 'TestDatabaseNoParent' + $mockDatabaseWithoutParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseWithoutParent.Name = 'TestDatabaseNoParent' - { New-SqlDscFileGroup -Database $mockDatabaseWithoutParent -Name 'InvalidFileGroup' -Confirm:$false } | - Should -Throw -ExpectedMessage '*must have a Server object attached to the Parent property*' -ErrorId 'NSDFG0003,New-SqlDscFileGroup' - } + { New-SqlDscFileGroup -Database $mockDatabaseWithoutParent -Name 'InvalidFileGroup' -Confirm:$false } | + Should -Throw -ExpectedMessage '*must have a Server object attached to the Parent property*' -ErrorId 'NSDFG0003,New-SqlDscFileGroup' } It 'Should return null when WhatIf is specified' { - InModuleScope -Parameters @{ - mockDatabaseObject = $mockDatabaseObject - } -ScriptBlock { - param ($mockDatabaseObject) - - $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'WhatIfFileGroup' -WhatIf + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -Name 'WhatIfFileGroup' -WhatIf - $result | Should -BeNullOrEmpty - } + $result | Should -BeNullOrEmpty } } Context 'When creating a standalone FileGroup' { It 'Should create a standalone FileGroup without a Database' { - InModuleScope -ScriptBlock { - $result = New-SqlDscFileGroup -Name 'StandaloneFileGroup' + $result = New-SqlDscFileGroup -Name 'StandaloneFileGroup' - $result | Should -Not -BeNullOrEmpty - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' - $result.Name | Should -Be 'StandaloneFileGroup' - $result.Parent | Should -BeNullOrEmpty - } + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'StandaloneFileGroup' + $result.Parent | Should -BeNullOrEmpty } It 'Should create a standalone PRIMARY FileGroup' { - InModuleScope -ScriptBlock { - $result = New-SqlDscFileGroup -Name 'PRIMARY' + $result = New-SqlDscFileGroup -Name 'PRIMARY' - $result | Should -Not -BeNullOrEmpty - $result.Name | Should -Be 'PRIMARY' - $result.Parent | Should -BeNullOrEmpty - } + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'PRIMARY' + $result.Parent | Should -BeNullOrEmpty } } From 60bfadba95ce2c209c47a2a59890cbf21aad4733 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:53:11 +0100 Subject: [PATCH 70/87] Add parameter set validation test for ConvertTo-SqlDscFileGroup command --- .../Public/ConvertTo-SqlDscFileGroup.Tests.ps1 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 index 37239e0639..a850073794 100644 --- a/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/ConvertTo-SqlDscFileGroup.Tests.ps1 @@ -147,6 +147,23 @@ Describe 'ConvertTo-SqlDscFileGroup' -Tag 'Public' { $commandInfo = Get-Command -Name 'ConvertTo-SqlDscFileGroup' } + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-DatabaseObject] [-FileGroupSpec] []' + } + ) { + $result = $commandInfo.ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + It 'Should have DatabaseObject as a mandatory parameter' { $parameterInfo = $commandInfo.Parameters['DatabaseObject'] $parameterInfo.Attributes.Mandatory | Should -Contain $true From 12f1994159cf321ee2e3290bcc640dedb13b8506 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 18:59:13 +0100 Subject: [PATCH 71/87] Refactor terminal profile settings in VSCode configuration for clarity --- .vscode/settings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 03c5688811..68a53391b6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -175,10 +175,10 @@ } }, "chat.tools.terminal.terminalProfile.osx": { - "path": "pwsh", // bash instead of zsh - "args": [], // non-login instead of login on macOS + "path": "pwsh", + "args": [], "env": { - "COPILOT": "1" // environment variable that can be used in init scripts + "COPILOT": "1" } }, "chat.tools.terminal.terminalProfile.linux": { From 2737346074847606d34f49983d449320950c973f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 19:11:21 +0100 Subject: [PATCH 72/87] Update output documentation for cmdlets to clarify return types and conditions --- ...unity-style-guidelines-pester.instructions.md | 2 +- source/Public/Add-SqlDscFileGroup.ps1 | 11 +++++++++-- source/Public/New-SqlDscDataFile.ps1 | 16 +++++++++++++--- source/Public/New-SqlDscFileGroup.ps1 | 9 ++++++++- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md index a25b3d95d8..55193b9f1f 100644 --- a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md @@ -28,7 +28,7 @@ applyTo: "**/*.[Tt]ests.ps1" - Mock variables prefix: 'mock' ## Structure & Scope -- Public commands: Never use `InModuleScope` (unless retrieving localized strings) +- Public commands: Never use `InModuleScope` (unless retrieving localized strings or creating an object using an internal class) - Private functions/class resources: Always use `InModuleScope` - Each class method = separate `Context` block - Each scenario = separate `Context` block diff --git a/source/Public/Add-SqlDscFileGroup.ps1 b/source/Public/Add-SqlDscFileGroup.ps1 index c5fae30b50..e81f0e0c69 100644 --- a/source/Public/Add-SqlDscFileGroup.ps1 +++ b/source/Public/Add-SqlDscFileGroup.ps1 @@ -21,12 +21,19 @@ Specifies that the FileGroup should be added without confirmation. .INPUTS - [Microsoft.SqlServer.Management.Smo.FileGroup] + Microsoft.SqlServer.Management.Smo.FileGroup FileGroup objects that will be added to the Database. .OUTPUTS - None, or [Microsoft.SqlServer.Management.Smo.FileGroup[]] if PassThru is specified. + None + + This cmdlet does not generate output by default. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.FileGroup[] + + When the PassThru parameter is specified, returns the FileGroup objects that were added. .EXAMPLE Add-SqlDscFileGroup -Database $database -FileGroup $fileGroup diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index 2d257e1cd4..0899c9b71d 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -105,9 +105,19 @@ Creates a DatabaseFileSpec object with all properties set directly via parameters. .OUTPUTS - None. Unless -PassThru is specified for the Standard or FromSpec parameter - sets, in which case it returns `[Microsoft.SqlServer.Management.Smo.DataFile]`. - When using the -AsSpec parameter, always returns `[DatabaseFileSpec]`. + None + + This cmdlet does not generate output by default when using Standard or FromSpec parameter sets without PassThru. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.DataFile + + When using the Standard or FromSpec parameter sets with the PassThru parameter. + + .OUTPUTS + DatabaseFileSpec + + When using the AsSpec parameter to create a specification object. #> function New-SqlDscDataFile { diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index 7de29d4be1..eb089116b5 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -73,7 +73,14 @@ Creates a DatabaseFileGroupSpec object with files and properties set directly via parameters. .OUTPUTS - `[Microsoft.SqlServer.Management.Smo.FileGroup]` or `[DatabaseFileGroupSpec]` if -AsSpec is specified. + Microsoft.SqlServer.Management.Smo.FileGroup + + When creating a FileGroup with or without an associated Database (not using -AsSpec). + + .OUTPUTS + DatabaseFileGroupSpec + + When using the -AsSpec parameter to create a specification object. #> function New-SqlDscFileGroup { From 973dc55c8f7d5e2f1558259268662f6a64ebb742 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 19:12:31 +0100 Subject: [PATCH 73/87] Refactor DatabaseFileGroupSpec tests to improve variable scoping and clarity --- tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 b/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 index 4d7fbe78be..cd3250756d 100644 --- a/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 +++ b/tests/Unit/Classes/DatabaseFileGroupSpec.Tests.ps1 @@ -26,15 +26,19 @@ BeforeDiscovery { BeforeAll { $script:dscModuleName = 'SqlServerDsc' - $env:SqlServerDscCI = $true + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' - $script:moduleUnderTest = Import-Module -Name $script:dscModuleName -PassThru -Force -ErrorAction 'Stop' + $env:SqlServerDscCI = $true $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName } AfterAll { $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') Remove-Item -Path 'env:SqlServerDscCI' @@ -45,7 +49,7 @@ AfterAll { Describe 'DatabaseFileGroupSpec' -Tag 'DatabaseFileGroupSpec' { Context 'When instantiating the class' { It 'Should create an instance with default constructor' { - $script:instance = InModuleScope -ScriptBlock { + $instance = InModuleScope -ScriptBlock { [DatabaseFileGroupSpec]::new() } @@ -54,7 +58,7 @@ Describe 'DatabaseFileGroupSpec' -Tag 'DatabaseFileGroupSpec' { } It 'Should create an instance with Name only' { - $script:instance = InModuleScope -ScriptBlock { + $instance = InModuleScope -ScriptBlock { [DatabaseFileGroupSpec]::new('PRIMARY') } @@ -64,7 +68,7 @@ Describe 'DatabaseFileGroupSpec' -Tag 'DatabaseFileGroupSpec' { } It 'Should create an instance with Name and Files array' { - $script:instance = InModuleScope -ScriptBlock { + $instance = InModuleScope -ScriptBlock { $files = @( [DatabaseFileSpec]::new('File1', 'C:\Data\File1.mdf') ) From 1bcc175b451eeba75cf589bb779e6d1a856dde18 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 19:47:25 +0100 Subject: [PATCH 74/87] Add input documentation and validation for DataFileSpec parameter in New-SqlDscDataFile --- source/Public/New-SqlDscDataFile.ps1 | 8 +++++++- tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/source/Public/New-SqlDscDataFile.ps1 b/source/Public/New-SqlDscDataFile.ps1 index 0899c9b71d..3a4489b307 100644 --- a/source/Public/New-SqlDscDataFile.ps1 +++ b/source/Public/New-SqlDscDataFile.ps1 @@ -104,6 +104,11 @@ Creates a DatabaseFileSpec object with all properties set directly via parameters. + .INPUTS + None + + This cmdlet does not accept input from the pipeline. + .OUTPUTS None @@ -145,7 +150,8 @@ function New-SqlDscDataFile $FileName, [Parameter(Mandatory = $true, ParameterSetName = 'FromSpec')] - [System.Object] + [ValidateNotNull()] + [DatabaseFileSpec] $DataFileSpec, [Parameter(Mandatory = $true, ParameterSetName = 'AsSpec')] diff --git a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 index 0a31233995..0127cb0629 100644 --- a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 @@ -140,7 +140,7 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { It 'Should have the correct parameters in parameter set FromSpec' -ForEach @( @{ ExpectedParameterSetName = 'FromSpec' - ExpectedParameters = '-FileGroup -DataFileSpec [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + ExpectedParameters = '-FileGroup -DataFileSpec [-PassThru] [-Force] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscDataFile').ParameterSets | @@ -213,6 +213,12 @@ Describe 'New-SqlDscDataFile' -Tag 'Public' { $fromSpecSetAttribute.Mandatory | Should -BeTrue } + It 'Should have ValidateNotNull attribute on DataFileSpec parameter' { + $parameterInfo = (Get-Command -Name 'New-SqlDscDataFile').Parameters['DataFileSpec'] + $validateNotNullAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateNotNullAttribute] } + $validateNotNullAttribute | Should -Not -BeNullOrEmpty + } + It 'Should have AsSpec as a mandatory parameter in AsSpec parameter set' { $parameterInfo = (Get-Command -Name 'New-SqlDscDataFile').Parameters['AsSpec'] $asSpecSetAttribute = $parameterInfo.Attributes | Where-Object { $_.ParameterSetName -eq 'AsSpec' } From 123856e414f6283087f67e5ea70732ba02864f34 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 19:48:42 +0100 Subject: [PATCH 75/87] Return FileGroup object when PassThru parameter is specified in Add-SqlDscFileGroup function --- source/Public/Add-SqlDscFileGroup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Public/Add-SqlDscFileGroup.ps1 b/source/Public/Add-SqlDscFileGroup.ps1 index e81f0e0c69..7337a8fcf9 100644 --- a/source/Public/Add-SqlDscFileGroup.ps1 +++ b/source/Public/Add-SqlDscFileGroup.ps1 @@ -88,7 +88,7 @@ function Add-SqlDscFileGroup if ($PassThru.IsPresent) { - $fileGroupObject + return $fileGroupObject } } } From 43fdbc502a8833d3fd06d60e64cefe709358f058 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 19:55:13 +0100 Subject: [PATCH 76/87] Update New-SqlDscFileGroup to set 'Standalone' as the default parameter set and add it as a mandatory parameter set --- source/Public/New-SqlDscFileGroup.ps1 | 5 +++-- tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index eb089116b5..fdf1d5dd43 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -85,7 +85,7 @@ function New-SqlDscFileGroup { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] - [CmdletBinding(DefaultParameterSetName = 'AsSpec', SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [CmdletBinding(DefaultParameterSetName = 'Standalone', SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([Microsoft.SqlServer.Management.Smo.FileGroup])] [OutputType([DatabaseFileGroupSpec])] param @@ -96,6 +96,7 @@ function New-SqlDscFileGroup $Database, [Parameter(Mandatory = $true, ParameterSetName = 'WithDatabase')] + [Parameter(Mandatory = $true, ParameterSetName = 'Standalone')] [Parameter(Mandatory = $true, ParameterSetName = 'AsSpec')] [ValidateNotNullOrEmpty()] [System.String] @@ -105,7 +106,7 @@ function New-SqlDscFileGroup [System.Object] $FileGroupSpec, - [Parameter(ParameterSetName = 'AsSpec')] + [Parameter(Mandatory = $true, ParameterSetName = 'AsSpec')] [System.Management.Automation.SwitchParameter] $AsSpec, diff --git a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 index 34900ab62a..cc091d54e2 100644 --- a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 @@ -136,17 +136,18 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { $parameterInfo.Attributes.Mandatory | Should -Contain $true } - It 'Should have three parameter sets (WithDatabase, WithDatabaseFromSpec, AsSpec)' { + It 'Should have four parameter sets (WithDatabase, WithDatabaseFromSpec, AsSpec, Standalone)' { $command = Get-Command -Name 'New-SqlDscFileGroup' - $command.ParameterSets.Count | Should -Be 3 + $command.ParameterSets.Count | Should -Be 4 $command.ParameterSets.Name | Should -Contain 'WithDatabase' $command.ParameterSets.Name | Should -Contain 'WithDatabaseFromSpec' $command.ParameterSets.Name | Should -Contain 'AsSpec' + $command.ParameterSets.Name | Should -Contain 'Standalone' } - It 'Should have AsSpec as the default parameter set' { + It 'Should have Standalone as the default parameter set' { $command = Get-Command -Name 'New-SqlDscFileGroup' - $command.DefaultParameterSet | Should -Be 'AsSpec' + $command.DefaultParameterSet | Should -Be 'Standalone' } It 'Should support ShouldProcess' { From ae8a1b6fdd16907434a00ffebcbbf259d2bfea16 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 19:56:15 +0100 Subject: [PATCH 77/87] Add input documentation for New-SqlDscFileGroup and update FileGroupSpec parameter type --- source/Public/New-SqlDscFileGroup.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/Public/New-SqlDscFileGroup.ps1 b/source/Public/New-SqlDscFileGroup.ps1 index fdf1d5dd43..950eab88f4 100644 --- a/source/Public/New-SqlDscFileGroup.ps1 +++ b/source/Public/New-SqlDscFileGroup.ps1 @@ -72,6 +72,11 @@ Creates a DatabaseFileGroupSpec object with files and properties set directly via parameters. + .INPUTS + None + + This cmdlet does not accept input from the pipeline. + .OUTPUTS Microsoft.SqlServer.Management.Smo.FileGroup @@ -103,7 +108,7 @@ function New-SqlDscFileGroup $Name, [Parameter(Mandatory = $true, ParameterSetName = 'WithDatabaseFromSpec')] - [System.Object] + [DatabaseFileGroupSpec] $FileGroupSpec, [Parameter(Mandatory = $true, ParameterSetName = 'AsSpec')] From 3127195cbf70c813527ea05660e63c413a808986 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 20:11:52 +0100 Subject: [PATCH 78/87] Add tests for New-SqlDscFileGroup to validate FileGroupSpec creation and parameter sets --- .../Unit/Public/New-SqlDscFileGroup.Tests.ps1 | 260 +++++++++++++++++- 1 file changed, 258 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 index cc091d54e2..6bfc0dc156 100644 --- a/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscFileGroup.Tests.ps1 @@ -119,23 +119,278 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { } } + Context 'When creating a FileGroup specification using AsSpec' { + It 'Should create a DatabaseFileGroupSpec object' { + InModuleScope -ScriptBlock { + $result = New-SqlDscFileGroup -Name 'MyFileGroup' -AsSpec + + $result | Should -Not -BeNullOrEmpty + $result.GetType().Name | Should -Be 'DatabaseFileGroupSpec' + $result.Name | Should -Be 'MyFileGroup' + $result.Files | Should -BeNullOrEmpty + $result.ReadOnly | Should -BeFalse + $result.IsDefault | Should -BeFalse + } + } + + It 'Should create a DatabaseFileGroupSpec with ReadOnly property set' { + InModuleScope -ScriptBlock { + $result = New-SqlDscFileGroup -Name 'ReadOnlyFileGroup' -AsSpec -ReadOnly + + $result | Should -Not -BeNullOrEmpty + $result.GetType().Name | Should -Be 'DatabaseFileGroupSpec' + $result.Name | Should -Be 'ReadOnlyFileGroup' + $result.ReadOnly | Should -BeTrue + $result.IsDefault | Should -BeFalse + } + } + + It 'Should create a DatabaseFileGroupSpec with IsDefault property set' { + InModuleScope -ScriptBlock { + $result = New-SqlDscFileGroup -Name 'PRIMARY' -AsSpec -IsDefault + + $result | Should -Not -BeNullOrEmpty + $result.GetType().Name | Should -Be 'DatabaseFileGroupSpec' + $result.Name | Should -Be 'PRIMARY' + $result.IsDefault | Should -BeTrue + $result.ReadOnly | Should -BeFalse + } + } + + It 'Should create a DatabaseFileGroupSpec with Files property set' { + InModuleScope -ScriptBlock { + # Create mock DatabaseFileSpec objects + $mockFileSpec1 = [DatabaseFileSpec]::new() + $mockFileSpec1.Name = 'TestFile1' + $mockFileSpec1.FileName = 'C:\SQLData\TestFile1.ndf' + + $mockFileSpec2 = [DatabaseFileSpec]::new() + $mockFileSpec2.Name = 'TestFile2' + $mockFileSpec2.FileName = 'C:\SQLData\TestFile2.ndf' + + $result = New-SqlDscFileGroup -Name 'DataFileGroup' -AsSpec -Files @($mockFileSpec1, $mockFileSpec2) + + $result | Should -Not -BeNullOrEmpty + $result.GetType().Name | Should -Be 'DatabaseFileGroupSpec' + $result.Name | Should -Be 'DataFileGroup' + $result.Files | Should -HaveCount 2 + $result.Files[0].Name | Should -Be 'TestFile1' + $result.Files[1].Name | Should -Be 'TestFile2' + } + } + + It 'Should create a DatabaseFileGroupSpec with all properties set' { + InModuleScope -ScriptBlock { + $mockFileSpec = [DatabaseFileSpec]::new() + $mockFileSpec.Name = 'PrimaryFile' + $mockFileSpec.FileName = 'C:\SQLData\PrimaryFile.mdf' + + $result = New-SqlDscFileGroup -Name 'PRIMARY' -AsSpec -Files @($mockFileSpec) -IsDefault -ReadOnly + + $result | Should -Not -BeNullOrEmpty + $result.GetType().Name | Should -Be 'DatabaseFileGroupSpec' + $result.Name | Should -Be 'PRIMARY' + $result.Files | Should -HaveCount 1 + $result.IsDefault | Should -BeTrue + $result.ReadOnly | Should -BeTrue + } + } + } + + Context 'When creating a FileGroup from a FileGroupSpec with Database' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject.Name = 'TestDatabase' + $mockDatabaseObject.Parent = $mockServerObject + + # Mock ConvertTo-SqlDscFileGroup + Mock -CommandName ConvertTo-SqlDscFileGroup -MockWith { + $fileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' -ArgumentList $DatabaseObject, $FileGroupSpec.Name + $fileGroup.ReadOnly = $FileGroupSpec.ReadOnly + $fileGroup.IsDefault = $FileGroupSpec.IsDefault + return $fileGroup + } + } + + It 'Should create a FileGroup from a FileGroupSpec object' { + $mockFileGroupSpec = InModuleScope -ScriptBlock { + $spec = [DatabaseFileGroupSpec]::new() + $spec.Name = 'SpecFileGroup' + $spec.ReadOnly = $false + $spec.IsDefault = $false + return $spec + } + + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -FileGroupSpec $mockFileGroupSpec -Confirm:$false + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'SpecFileGroup' + $result.Parent | Should -Be $mockDatabaseObject + + Should -Invoke -CommandName ConvertTo-SqlDscFileGroup -ParameterFilter { + $DatabaseObject -eq $mockDatabaseObject -and $FileGroupSpec.Name -eq 'SpecFileGroup' + } -Exactly -Times 1 -Scope It + } + + It 'Should create a FileGroup from a FileGroupSpec with properties set' { + $mockFileGroupSpec = InModuleScope -ScriptBlock { + $spec = [DatabaseFileGroupSpec]::new() + $spec.Name = 'ReadOnlySpec' + $spec.ReadOnly = $true + $spec.IsDefault = $false + return $spec + } + + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -FileGroupSpec $mockFileGroupSpec -Force + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.FileGroup' + $result.Name | Should -Be 'ReadOnlySpec' + $result.ReadOnly | Should -BeTrue + + Should -Invoke -CommandName ConvertTo-SqlDscFileGroup -ParameterFilter { + $DatabaseObject -eq $mockDatabaseObject -and $FileGroupSpec.ReadOnly -eq $true + } -Exactly -Times 1 -Scope It + } + + It 'Should throw terminating error when Database object has no Parent property set' { + $mockDatabaseWithoutParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseWithoutParent.Name = 'TestDatabaseNoParent' + + $mockFileGroupSpec = InModuleScope -ScriptBlock { + $spec = [DatabaseFileGroupSpec]::new() + $spec.Name = 'FailFileGroup' + return $spec + } + + { New-SqlDscFileGroup -Database $mockDatabaseWithoutParent -FileGroupSpec $mockFileGroupSpec -Confirm:$false } | + Should -Throw -ExpectedMessage '*must have a Server object attached to the Parent property*' -ErrorId 'NSDFG0003,New-SqlDscFileGroup' + } + + It 'Should return null when WhatIf is specified' { + $mockFileGroupSpec = InModuleScope -ScriptBlock { + $spec = [DatabaseFileGroupSpec]::new() + $spec.Name = 'WhatIfSpec' + return $spec + } + + $result = New-SqlDscFileGroup -Database $mockDatabaseObject -FileGroupSpec $mockFileGroupSpec -WhatIf + + $result | Should -BeNullOrEmpty + } + } + Context 'Parameter validation' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'WithDatabase' + ExpectedParameters = '-Database -Name [-Force] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'WithDatabaseFromSpec' + ExpectedParameters = '-Database -FileGroupSpec [-Force] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'AsSpec' + ExpectedParameters = '-Name -AsSpec [-Files ] [-ReadOnly] [-IsDefault] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'Standalone' + ExpectedParameters = '-Name [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'New-SqlDscFileGroup').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + It 'Should have Database as a mandatory parameter in WithDatabase parameter set' { $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] $parameterSetInfo = $parameterInfo.ParameterSets['WithDatabase'] $parameterSetInfo.IsMandatory | Should -BeTrue } + It 'Should have Database as a mandatory parameter in WithDatabaseFromSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] + $parameterSetInfo = $parameterInfo.ParameterSets['WithDatabaseFromSpec'] + $parameterSetInfo.IsMandatory | Should -BeTrue + } + It 'Should have Database parameter not be in Standalone parameter set' { $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] $parameterInfo.ParameterSets.Keys | Should -Not -Contain 'Standalone' } + It 'Should have Database parameter not be in AsSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Database'] + $parameterInfo.ParameterSets.Keys | Should -Not -Contain 'AsSpec' + } + It 'Should have Name as a mandatory parameter' { $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Name'] $parameterInfo.Attributes.Mandatory | Should -Contain $true } + It 'Should have Name parameter in WithDatabase parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Name'] + $parameterInfo.ParameterSets.Keys | Should -Contain 'WithDatabase' + } + + It 'Should have Name parameter in Standalone parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Name'] + $parameterInfo.ParameterSets.Keys | Should -Contain 'Standalone' + } + + It 'Should have Name parameter in AsSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Name'] + $parameterInfo.ParameterSets.Keys | Should -Contain 'AsSpec' + } + + It 'Should have Name parameter not be in WithDatabaseFromSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Name'] + $parameterInfo.ParameterSets.Keys | Should -Not -Contain 'WithDatabaseFromSpec' + } + + It 'Should have FileGroupSpec as a mandatory parameter in WithDatabaseFromSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['FileGroupSpec'] + $parameterSetInfo = $parameterInfo.ParameterSets['WithDatabaseFromSpec'] + $parameterSetInfo.IsMandatory | Should -BeTrue + } + + It 'Should have AsSpec as a mandatory parameter in AsSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['AsSpec'] + $parameterSetInfo = $parameterInfo.ParameterSets['AsSpec'] + $parameterSetInfo.IsMandatory | Should -BeTrue + } + + It 'Should have Files parameter only in AsSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Files'] + $parameterInfo.ParameterSets.Keys | Should -Contain 'AsSpec' + $parameterInfo.ParameterSets.Keys | Should -HaveCount 1 + } + + It 'Should have ReadOnly parameter only in AsSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['ReadOnly'] + $parameterInfo.ParameterSets.Keys | Should -Contain 'AsSpec' + $parameterInfo.ParameterSets.Keys | Should -HaveCount 1 + } + + It 'Should have IsDefault parameter only in AsSpec parameter set' { + $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['IsDefault'] + $parameterInfo.ParameterSets.Keys | Should -Contain 'AsSpec' + $parameterInfo.ParameterSets.Keys | Should -HaveCount 1 + } + It 'Should have four parameter sets (WithDatabase, WithDatabaseFromSpec, AsSpec, Standalone)' { $command = Get-Command -Name 'New-SqlDscFileGroup' $command.ParameterSets.Count | Should -Be 4 @@ -156,11 +411,12 @@ Describe 'New-SqlDscFileGroup' -Tag 'Public' { $command.Parameters.ContainsKey('Confirm') | Should -BeTrue } - It 'Should have Force parameter only in WithDatabase parameter set' { + It 'Should have Force parameter only in WithDatabase and WithDatabaseFromSpec parameter sets' { $parameterInfo = (Get-Command -Name 'New-SqlDscFileGroup').Parameters['Force'] $parameterInfo | Should -Not -BeNullOrEmpty $parameterInfo.ParameterSets.Keys | Should -Contain 'WithDatabase' - $parameterInfo.ParameterSets.Keys | Should -Not -Contain 'Standalone' + $parameterInfo.ParameterSets.Keys | Should -Contain 'WithDatabaseFromSpec' + $parameterInfo.ParameterSets.Keys | Should -HaveCount 2 } It 'Should have ConfirmImpact set to High' { From 93f434e1050a2582f0c113e33031b313d20e3460 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 20:12:46 +0100 Subject: [PATCH 79/87] Return FileGroup object when PassThru parameter is specified in Add-SqlDscFileGroup function --- source/Public/Add-SqlDscFileGroup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Public/Add-SqlDscFileGroup.ps1 b/source/Public/Add-SqlDscFileGroup.ps1 index 7337a8fcf9..e81f0e0c69 100644 --- a/source/Public/Add-SqlDscFileGroup.ps1 +++ b/source/Public/Add-SqlDscFileGroup.ps1 @@ -88,7 +88,7 @@ function Add-SqlDscFileGroup if ($PassThru.IsPresent) { - return $fileGroupObject + $fileGroupObject } } } From c6ac9a04f859b62992182cbf9a85187a5560b84b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 22 Nov 2025 20:13:41 +0100 Subject: [PATCH 80/87] Refactor variable names for consistency in New-SqlDscDataFile.Tests.ps1 --- tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 index 0127cb0629..28606e4d2b 100644 --- a/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDataFile.Tests.ps1 @@ -24,18 +24,18 @@ BeforeDiscovery { } BeforeAll { - $script:dscModuleName = 'SqlServerDsc' + $script:moduleName = 'SqlServerDsc' $env:SqlServerDscCI = $true - Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' # Loading mocked classes Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName } AfterAll { @@ -44,7 +44,7 @@ AfterAll { $PSDefaultParameterValues.Remove('Should:ModuleName') # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + Get-Module -Name $script:moduleName -All | Remove-Module -Force Remove-Item -Path 'env:SqlServerDscCI' } From bcaa6fac9e31b544e06b5e4e6f76b6d6b7bd9c5c Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 23 Nov 2025 08:55:34 +0100 Subject: [PATCH 81/87] Add automatic file group generation in New-SqlDscDatabaseSnapshot when not specified --- source/Public/New-SqlDscDatabaseSnapshot.ps1 | 63 ++++++- ...lDscDatabaseSnapshot.Integration.Tests.ps1 | 7 +- .../New-SqlDscDatabaseSnapshot.Tests.ps1 | 160 +++++++++++++++++- 3 files changed, 216 insertions(+), 14 deletions(-) diff --git a/source/Public/New-SqlDscDatabaseSnapshot.ps1 b/source/Public/New-SqlDscDatabaseSnapshot.ps1 index 2c63f8201f..cd3002f686 100644 --- a/source/Public/New-SqlDscDatabaseSnapshot.ps1 +++ b/source/Public/New-SqlDscDatabaseSnapshot.ps1 @@ -188,20 +188,73 @@ function New-SqlDscDatabaseSnapshot } } + # If FileGroup is not specified, automatically create file groups based on source database + if (-not $PSBoundParameters.ContainsKey('FileGroup')) + { + # Get the source database object + $getSqlDscDatabaseParameters = @{ + ServerObject = $ServerObject + Name = $DatabaseName + ErrorAction = 'Stop' + } + + if ($PSCmdlet.ParameterSetName -eq 'ServerObject' -and $Refresh.IsPresent) + { + $getSqlDscDatabaseParameters['Refresh'] = $true + } + + $sourceDatabase = Get-SqlDscDatabase @getSqlDscDatabaseParameters + + # Get the default data directory for sparse files + $defaultDataDirectory = $ServerObject.Settings.DefaultFile + + if (-not $defaultDataDirectory) + { + $defaultDataDirectory = $ServerObject.Information.MasterDBPath + } + + # Create file group specifications for all file groups in the source database + $generatedFileGroups = [System.Collections.Generic.List[DatabaseFileGroupSpec]]::new() + + foreach ($sourceFileGroup in $sourceDatabase.FileGroups) + { + $fileSpecs = [System.Collections.Generic.List[DatabaseFileSpec]]::new() + + foreach ($sourceFile in $sourceFileGroup.Files) + { + # Use the same physical filename as the source file, but with .ss extension + $sourceFileName = [System.IO.Path]::GetFileNameWithoutExtension($sourceFile.FileName) + $sparseFileName = '{0}.ss' -f $sourceFileName + $sparseFilePath = Join-Path -Path $defaultDataDirectory -ChildPath $sparseFileName + + # Create a file spec using the same logical name as the source database file + $fileSpec = [DatabaseFileSpec]::new() + $fileSpec.Name = $sourceFile.Name + $fileSpec.FileName = $sparseFilePath + + $fileSpecs.Add($fileSpec) + } + + # Create file group spec + $fileGroupSpec = [DatabaseFileGroupSpec]::new($sourceFileGroup.Name) + $fileGroupSpec.Files = $fileSpecs.ToArray() + + $generatedFileGroups.Add($fileGroupSpec) + } + + $FileGroup = $generatedFileGroups.ToArray() + } + # Create the snapshot using New-SqlDscDatabase $newSqlDscDatabaseParameters = @{ ServerObject = $ServerObject Name = $Name DatabaseSnapshotBaseName = $DatabaseName + FileGroup = $FileGroup Force = $Force WhatIf = $WhatIfPreference } - if ($PSBoundParameters.ContainsKey('FileGroup')) - { - $newSqlDscDatabaseParameters['FileGroup'] = $FileGroup - } - if ($PSCmdlet.ParameterSetName -eq 'ServerObject' -and $Refresh.IsPresent) { $newSqlDscDatabaseParameters['Refresh'] = $true diff --git a/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 index 514d773466..04a93cc6e2 100644 --- a/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 @@ -141,9 +141,14 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag @('Integration_SQL2017', 'Integration } It 'Should create a database snapshot with custom sparse file location' { + # Get the logical name of the source database's primary file + $sourceDatabase = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentSourceDatabase -ErrorAction 'Stop' + $sourceLogicalFileName = $sourceDatabase.FileGroups['PRIMARY'].Files[0].Name + # Create PRIMARY filegroup with sparse file using -AsSpec + # IMPORTANT: Must use the same logical name as the source database file $sparseFilePath = Join-Path -Path $script:dataDirectory -ChildPath ($script:testSnapshotNameWithFileGroup + '_Sparse.ss') - $dataFileSpec = New-SqlDscDataFile -Name ($script:testSnapshotNameWithFileGroup + '_Sparse') -FileName $sparseFilePath -AsSpec + $dataFileSpec = New-SqlDscDataFile -Name $sourceLogicalFileName -FileName $sparseFilePath -AsSpec $primaryFileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($dataFileSpec) -AsSpec # Create snapshot with custom file group diff --git a/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 b/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 index 90d881f65f..caa3ca804d 100644 --- a/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 @@ -58,18 +58,34 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Enterprise Edition' -Force $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'VersionMajor' -Value 15 -Force - # Mock source database + # Mock Settings for default data directory + $mockSettings = New-Object -TypeName 'PSObject' + $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force + + # Mock source database with file groups and files $mockSourceDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' $mockSourceDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'SourceDatabase' -Force - $mockServerObject | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { - return @{ - 'SourceDatabase' = $mockSourceDatabase - } | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { - # Mock implementation - } -PassThru -Force + # Mock file group and data file + $mockDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' + $mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'SourceDatabase' -Force + $mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/SourceDatabase.mdf' -Force + + $mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' + $mockFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force + $mockFileGroup | Add-Member -MemberType 'ScriptProperty' -Name 'Files' -Value { + return @($mockDataFile) } -Force + $mockSourceDatabase | Add-Member -MemberType 'ScriptProperty' -Name 'FileGroups' -Value { + return @($mockFileGroup) + } -Force + + Mock -CommandName 'Get-SqlDscDatabase' -MockWith { + return $mockSourceDatabase + } + Mock -CommandName 'New-SqlDscDatabase' -MockWith { $mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force @@ -85,6 +101,26 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $result.Name | Should -Be 'TestSnapshot' $result.DatabaseSnapshotBaseName | Should -Be 'SourceDatabase' Should -Invoke -CommandName 'New-SqlDscDatabase' -Exactly -Times 1 + Should -Invoke -CommandName 'Get-SqlDscDatabase' -Exactly -Times 1 + } + + It 'Should auto-generate file groups when not specified' { + $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -Force + + Should -Invoke -CommandName 'Get-SqlDscDatabase' -ParameterFilter { + $ServerObject.InstanceName -eq 'TestInstance' -and + $Name -eq 'SourceDatabase' -and + $ErrorAction -eq 'Stop' + } -Exactly -Times 1 + + Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { + $FileGroup -and + $FileGroup.Count -eq 1 -and + $FileGroup[0].Name -eq 'PRIMARY' -and + $FileGroup[0].Files.Count -eq 1 -and + $FileGroup[0].Files[0].Name -eq 'SourceDatabase' -and + $FileGroup[0].Files[0].FileName -eq '/var/opt/mssql/data/SourceDatabase.ss' + } -Exactly -Times 1 } It 'Should pass the correct parameters to New-SqlDscDatabase' { @@ -98,9 +134,13 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { } -Exactly -Times 1 } - It 'Should pass Refresh parameter when specified' { + It 'Should pass Refresh parameter to Get-SqlDscDatabase when specified' { $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -Refresh -Force + Should -Invoke -CommandName 'Get-SqlDscDatabase' -ParameterFilter { + $Refresh -eq $true + } -Exactly -Times 1 + Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { $Refresh -eq $true } -Exactly -Times 1 @@ -122,10 +162,35 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -FileGroup @($mockFileGroupSpec) -Force + # Should not call Get-SqlDscDatabase when FileGroup is specified + Should -Invoke -CommandName 'Get-SqlDscDatabase' -Exactly -Times 0 -Scope It + Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { $FileGroup -and $FileGroup.Count -eq 1 -and $FileGroup[0].Name -eq 'PRIMARY' } -Exactly -Times 1 } + + It 'Should use MasterDBPath when DefaultFile is not set' { + # Create a server object without DefaultFile set + $mockServerObjectNoDefaultFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Enterprise' -Force + $mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Enterprise Edition' -Force + + $mockSettingsNoDefault = New-Object -TypeName 'PSObject' + $mockSettingsNoDefault | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value $null -Force + $mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettingsNoDefault -Force + + $mockInformation = New-Object -TypeName 'PSObject' + $mockInformation | Add-Member -MemberType 'NoteProperty' -Name 'MasterDBPath' -Value '/var/opt/mssql/master' -Force + $mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'Information' -Value $mockInformation -Force + + $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObjectNoDefaultFile -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -Force + + Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { + $FileGroup[0].Files[0].FileName -eq '/var/opt/mssql/master/SourceDatabase.ss' + } -Exactly -Times 1 + } } Context 'When creating a database snapshot using DatabaseObject parameter set' { @@ -135,10 +200,34 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Enterprise' -Force $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Enterprise Edition' -Force + # Mock Settings for default data directory + $mockSettings = New-Object -TypeName 'PSObject' + $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Parent' -Value $mockServerObject -Force + # Mock file group and data file for DatabaseObject + $mockDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' + $mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force + $mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/MyDatabase.mdf' -Force + + $mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' + $mockFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force + $mockFileGroup | Add-Member -MemberType 'ScriptProperty' -Name 'Files' -Value { + return @($mockDataFile) + } -Force + + $mockDatabaseObject | Add-Member -MemberType 'ScriptProperty' -Name 'FileGroups' -Value { + return @($mockFileGroup) + } -Force + + Mock -CommandName 'Get-SqlDscDatabase' -MockWith { + return $mockDatabaseObject + } + Mock -CommandName 'New-SqlDscDatabase' -MockWith { $mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force @@ -154,6 +243,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $result.Name | Should -Be 'TestSnapshot' $result.DatabaseSnapshotBaseName | Should -Be 'MyDatabase' Should -Invoke -CommandName 'New-SqlDscDatabase' -Exactly -Times 1 + Should -Invoke -CommandName 'Get-SqlDscDatabase' -Exactly -Times 1 } It 'Should pass the correct parameters to New-SqlDscDatabase from DatabaseObject' { @@ -211,6 +301,33 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $mockServerObjectDeveloper | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Standard' -Force $mockServerObjectDeveloper | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Developer Edition' -Force + # Mock Settings for default data directory + $mockSettings = New-Object -TypeName 'PSObject' + $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force + $mockServerObjectDeveloper | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force + + # Mock source database + $mockDevSourceDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDevSourceDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force + + $mockDevDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' + $mockDevDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force + $mockDevDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/MyDatabase.mdf' -Force + + $mockDevFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' + $mockDevFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force + $mockDevFileGroup | Add-Member -MemberType 'ScriptProperty' -Name 'Files' -Value { + return @($mockDevDataFile) + } -Force + + $mockDevSourceDatabase | Add-Member -MemberType 'ScriptProperty' -Name 'FileGroups' -Value { + return @($mockDevFileGroup) + } -Force + + Mock -CommandName 'Get-SqlDscDatabase' -MockWith { + return $mockDevSourceDatabase + } + Mock -CommandName 'New-SqlDscDatabase' -MockWith { $mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force @@ -235,6 +352,33 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $mockServerObjectEvaluation | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Standard' -Force $mockServerObjectEvaluation | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Evaluation Edition' -Force + # Mock Settings for default data directory + $mockSettings = New-Object -TypeName 'PSObject' + $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force + $mockServerObjectEvaluation | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force + + # Mock source database + $mockEvalSourceDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockEvalSourceDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force + + $mockEvalDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' + $mockEvalDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force + $mockEvalDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/MyDatabase.mdf' -Force + + $mockEvalFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' + $mockEvalFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force + $mockEvalFileGroup | Add-Member -MemberType 'ScriptProperty' -Name 'Files' -Value { + return @($mockEvalDataFile) + } -Force + + $mockEvalSourceDatabase | Add-Member -MemberType 'ScriptProperty' -Name 'FileGroups' -Value { + return @($mockEvalFileGroup) + } -Force + + Mock -CommandName 'Get-SqlDscDatabase' -MockWith { + return $mockEvalSourceDatabase + } + Mock -CommandName 'New-SqlDscDatabase' -MockWith { $mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' $mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force From 3dea101a2b23977f2b0b81390c9bc9bc30e38a57 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 23 Nov 2025 09:06:21 +0100 Subject: [PATCH 82/87] Refactor New-SqlDscDatabaseSnapshot to use New-SqlDscDataFile and New-SqlDscFileGroup for file and file group specifications --- source/Public/New-SqlDscDatabaseSnapshot.ps1 | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/source/Public/New-SqlDscDatabaseSnapshot.ps1 b/source/Public/New-SqlDscDatabaseSnapshot.ps1 index cd3002f686..dae812e287 100644 --- a/source/Public/New-SqlDscDatabaseSnapshot.ps1 +++ b/source/Public/New-SqlDscDatabaseSnapshot.ps1 @@ -228,18 +228,11 @@ function New-SqlDscDatabaseSnapshot $sparseFilePath = Join-Path -Path $defaultDataDirectory -ChildPath $sparseFileName # Create a file spec using the same logical name as the source database file - $fileSpec = [DatabaseFileSpec]::new() - $fileSpec.Name = $sourceFile.Name - $fileSpec.FileName = $sparseFilePath - - $fileSpecs.Add($fileSpec) + $fileSpecs.Add((New-SqlDscDataFile -Name $sourceFile.Name -FileName $sparseFilePath -AsSpec)) } # Create file group spec - $fileGroupSpec = [DatabaseFileGroupSpec]::new($sourceFileGroup.Name) - $fileGroupSpec.Files = $fileSpecs.ToArray() - - $generatedFileGroups.Add($fileGroupSpec) + $generatedFileGroups.Add((New-SqlDscFileGroup -Name $sourceFileGroup.Name -Files $fileSpecs.ToArray() -AsSpec)) } $FileGroup = $generatedFileGroups.ToArray() From 3cf2e5afe5d12f0d6a69d14bef6f41d8fc7c0d63 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 23 Nov 2025 10:20:39 +0100 Subject: [PATCH 83/87] Refactor New-SqlDscDatabaseSnapshot.Tests.ps1 to use platform-specific paths for mock data and master database --- .../New-SqlDscDatabaseSnapshot.Tests.ps1 | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 b/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 index caa3ca804d..dd2bd7a128 100644 --- a/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1 @@ -36,6 +36,18 @@ BeforeAll { $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName + + # Define platform-appropriate paths for use in mocks and assertions + if ($IsWindows -or $PSVersionTable.PSVersion.Major -le 5) + { + $script:mockDefaultDataPath = 'C:\mssql\data' + $script:mockMasterDbPath = 'C:\mssql\master' + } + else + { + $script:mockDefaultDataPath = '/var/opt/mssql/data' + $script:mockMasterDbPath = '/var/opt/mssql/master' + } } AfterAll { @@ -60,7 +72,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { # Mock Settings for default data directory $mockSettings = New-Object -TypeName 'PSObject' - $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force + $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value $script:mockDefaultDataPath -Force $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force # Mock source database with file groups and files @@ -70,7 +82,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { # Mock file group and data file $mockDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' $mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'SourceDatabase' -Force - $mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/SourceDatabase.mdf' -Force + $mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value (Join-Path -Path $script:mockDefaultDataPath -ChildPath 'SourceDatabase.mdf') -Force $mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' $mockFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force @@ -114,12 +126,13 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { } -Exactly -Times 1 Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { + $expectedPath = Join-Path -Path $script:mockDefaultDataPath -ChildPath 'SourceDatabase.ss' $FileGroup -and $FileGroup.Count -eq 1 -and $FileGroup[0].Name -eq 'PRIMARY' -and $FileGroup[0].Files.Count -eq 1 -and $FileGroup[0].Files[0].Name -eq 'SourceDatabase' -and - $FileGroup[0].Files[0].FileName -eq '/var/opt/mssql/data/SourceDatabase.ss' + $FileGroup[0].Files[0].FileName -eq $expectedPath } -Exactly -Times 1 } @@ -182,13 +195,14 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettingsNoDefault -Force $mockInformation = New-Object -TypeName 'PSObject' - $mockInformation | Add-Member -MemberType 'NoteProperty' -Name 'MasterDBPath' -Value '/var/opt/mssql/master' -Force + $mockInformation | Add-Member -MemberType 'NoteProperty' -Name 'MasterDBPath' -Value $script:mockMasterDbPath -Force $mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'Information' -Value $mockInformation -Force $result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObjectNoDefaultFile -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -Force Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter { - $FileGroup[0].Files[0].FileName -eq '/var/opt/mssql/master/SourceDatabase.ss' + $expectedPath = Join-Path -Path $script:mockMasterDbPath -ChildPath 'SourceDatabase.ss' + $FileGroup[0].Files[0].FileName -eq $expectedPath } -Exactly -Times 1 } } @@ -202,7 +216,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { # Mock Settings for default data directory $mockSettings = New-Object -TypeName 'PSObject' - $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force + $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value $script:mockDefaultDataPath -Force $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' @@ -212,7 +226,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { # Mock file group and data file for DatabaseObject $mockDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' $mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force - $mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/MyDatabase.mdf' -Force + $mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value (Join-Path -Path $script:mockDefaultDataPath -ChildPath 'MyDatabase.mdf') -Force $mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' $mockFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force @@ -303,7 +317,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { # Mock Settings for default data directory $mockSettings = New-Object -TypeName 'PSObject' - $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force + $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value $script:mockDefaultDataPath -Force $mockServerObjectDeveloper | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force # Mock source database @@ -312,7 +326,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $mockDevDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' $mockDevDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force - $mockDevDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/MyDatabase.mdf' -Force + $mockDevDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value (Join-Path -Path $script:mockDefaultDataPath -ChildPath 'MyDatabase.mdf') -Force $mockDevFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' $mockDevFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force @@ -354,7 +368,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { # Mock Settings for default data directory $mockSettings = New-Object -TypeName 'PSObject' - $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force + $mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value $script:mockDefaultDataPath -Force $mockServerObjectEvaluation | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force # Mock source database @@ -363,7 +377,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' { $mockEvalDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile' $mockEvalDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force - $mockEvalDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/MyDatabase.mdf' -Force + $mockEvalDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value (Join-Path -Path $script:mockDefaultDataPath -ChildPath 'MyDatabase.mdf') -Force $mockEvalFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup' $mockEvalFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force From 4a4b5d60527c64e1bbb0833eddc1017e85aedaba Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 23 Nov 2025 11:46:45 +0100 Subject: [PATCH 84/87] Add cleanup logic for existing snapshots in New-SqlDscDatabaseSnapshot integration tests --- ...ew-SqlDscDatabaseSnapshot.Integration.Tests.ps1 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 index 04a93cc6e2..2c4fb05dd4 100644 --- a/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 @@ -79,6 +79,16 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag @('Integration_SQL2017', 'Integration } Context 'When creating a database snapshot using ServerObject parameter set' { + AfterEach { + # Clean up snapshot created in this context to avoid file conflicts + $existingSnapshot = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testSnapshotName -ErrorAction 'SilentlyContinue' + + if ($existingSnapshot) + { + $null = Remove-SqlDscDatabase -DatabaseObject $existingSnapshot -Force -ErrorAction 'Stop' + } + } + It 'Should create a database snapshot successfully with minimal parameters' { $result = New-SqlDscDatabaseSnapshot -ServerObject $script:serverObject -Name $script:testSnapshotName -DatabaseName $script:persistentSourceDatabase -Force -ErrorAction 'Stop' @@ -94,6 +104,10 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag @('Integration_SQL2017', 'Integration } It 'Should throw error when trying to create a snapshot that already exists' { + # First create the snapshot + $null = New-SqlDscDatabaseSnapshot -ServerObject $script:serverObject -Name $script:testSnapshotName -DatabaseName $script:persistentSourceDatabase -Force -ErrorAction 'Stop' + + # Then try to create it again - should throw { New-SqlDscDatabaseSnapshot -ServerObject $script:serverObject -Name $script:testSnapshotName -DatabaseName $script:persistentSourceDatabase -Force -ErrorAction 'Stop' } | Should -Throw } From 87353f5f6e5f0056dc5a4620a0cbf473eaca313a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 24 Nov 2025 17:43:53 +0100 Subject: [PATCH 85/87] Move verbose logging for database snapshot creation to the end of the process in New-SqlDscDatabaseSnapshot --- source/Public/New-SqlDscDatabaseSnapshot.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Public/New-SqlDscDatabaseSnapshot.ps1 b/source/Public/New-SqlDscDatabaseSnapshot.ps1 index dae812e287..ab347dead9 100644 --- a/source/Public/New-SqlDscDatabaseSnapshot.ps1 +++ b/source/Public/New-SqlDscDatabaseSnapshot.ps1 @@ -146,8 +146,6 @@ function New-SqlDscDatabaseSnapshot $DatabaseName = $DatabaseObject.Name } - Write-Verbose -Message ($script:localizedData.DatabaseSnapshot_Create -f $Name, $DatabaseName, $ServerObject.InstanceName) - # Validate SQL Server edition supports snapshots $supportedEditions = @('Enterprise', 'Developer', 'EnterpriseCore', 'EnterpriseOrDeveloper') @@ -188,6 +186,8 @@ function New-SqlDscDatabaseSnapshot } } + Write-Verbose -Message ($script:localizedData.DatabaseSnapshot_Create -f $Name, $DatabaseName, $ServerObject.InstanceName) + # If FileGroup is not specified, automatically create file groups based on source database if (-not $PSBoundParameters.ContainsKey('FileGroup')) { From 4b60a7533c1de42672f73cbbd302bc7215f5f32f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 24 Nov 2025 17:44:01 +0100 Subject: [PATCH 86/87] Add cleanup logic for database snapshots in AfterEach blocks of integration tests --- ...lDscDatabaseSnapshot.Integration.Tests.ps1 | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 index 2c4fb05dd4..92638a9f35 100644 --- a/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1 @@ -119,6 +119,16 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag @('Integration_SQL2017', 'Integration $script:sourceDatabaseObject = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentSourceDatabase -ErrorAction 'Stop' } + AfterEach { + # Clean up snapshot created in this context to avoid file conflicts + $existingSnapshot = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testSnapshotNameFromDbObject -ErrorAction 'SilentlyContinue' + + if ($existingSnapshot) + { + $null = Remove-SqlDscDatabase -DatabaseObject $existingSnapshot -Force -ErrorAction 'Stop' + } + } + It 'Should create a database snapshot from DatabaseObject successfully' { $result = New-SqlDscDatabaseSnapshot -DatabaseObject $script:sourceDatabaseObject -Name $script:testSnapshotNameFromDbObject -Force -ErrorAction 'Stop' @@ -154,6 +164,16 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag @('Integration_SQL2017', 'Integration $script:sourceDatabaseForFG = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentSourceDatabase -ErrorAction 'Stop' } + AfterEach { + # Clean up snapshot created in this context to avoid file conflicts + $existingSnapshot = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testSnapshotNameWithFileGroup -ErrorAction 'SilentlyContinue' + + if ($existingSnapshot) + { + $null = Remove-SqlDscDatabase -DatabaseObject $existingSnapshot -Force -ErrorAction 'Stop' + } + } + It 'Should create a database snapshot with custom sparse file location' { # Get the logical name of the source database's primary file $sourceDatabase = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentSourceDatabase -ErrorAction 'Stop' From baafa8de6a078d59f7a51ac8501ad0438eba438e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 24 Nov 2025 18:27:07 +0100 Subject: [PATCH 87/87] Enhance documentation for New-SqlDscDatabaseSnapshot cmdlet to clarify automatic file group and file generation based on source database structure --- source/Public/New-SqlDscDatabaseSnapshot.ps1 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/source/Public/New-SqlDscDatabaseSnapshot.ps1 b/source/Public/New-SqlDscDatabaseSnapshot.ps1 index ab347dead9..5dff09929f 100644 --- a/source/Public/New-SqlDscDatabaseSnapshot.ps1 +++ b/source/Public/New-SqlDscDatabaseSnapshot.ps1 @@ -27,8 +27,10 @@ Specifies an array of DatabaseFileGroupSpec objects that define the file groups and data files for the database snapshot. Each DatabaseFileGroupSpec contains the file group name and an array of DatabaseFileSpec objects for the sparse files. - When not specified, SQL Server will create sparse files in the default data - directory with automatically generated names. + When not specified, the cmdlet automatically generates DatabaseFileGroupSpec + and DatabaseFileSpec entries based on the source database's file structure, + resulting in sparse files being created in the default data directory with + automatically generated names. .PARAMETER Force Specifies that the snapshot should be created without any confirmation. @@ -77,11 +79,18 @@ .INPUTS `[Microsoft.SqlServer.Management.Smo.Server]` + Specifies the SQL Server connection object to create the snapshot in. + + .INPUTS `[Microsoft.SqlServer.Management.Smo.Database]` + Specifies the source database object to create a snapshot from. + .OUTPUTS `[Microsoft.SqlServer.Management.Smo.Database]` + Returns the newly created database snapshot object. + .NOTES This command is for snapshot creation only and does not support modification of existing snapshots.