Skip to content

Commit bcaa6fa

Browse files
committed
Add automatic file group generation in New-SqlDscDatabaseSnapshot when not specified
1 parent c6ac9a0 commit bcaa6fa

File tree

3 files changed

+216
-14
lines changed

3 files changed

+216
-14
lines changed

source/Public/New-SqlDscDatabaseSnapshot.ps1

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,20 +188,73 @@ function New-SqlDscDatabaseSnapshot
188188
}
189189
}
190190

191+
# If FileGroup is not specified, automatically create file groups based on source database
192+
if (-not $PSBoundParameters.ContainsKey('FileGroup'))
193+
{
194+
# Get the source database object
195+
$getSqlDscDatabaseParameters = @{
196+
ServerObject = $ServerObject
197+
Name = $DatabaseName
198+
ErrorAction = 'Stop'
199+
}
200+
201+
if ($PSCmdlet.ParameterSetName -eq 'ServerObject' -and $Refresh.IsPresent)
202+
{
203+
$getSqlDscDatabaseParameters['Refresh'] = $true
204+
}
205+
206+
$sourceDatabase = Get-SqlDscDatabase @getSqlDscDatabaseParameters
207+
208+
# Get the default data directory for sparse files
209+
$defaultDataDirectory = $ServerObject.Settings.DefaultFile
210+
211+
if (-not $defaultDataDirectory)
212+
{
213+
$defaultDataDirectory = $ServerObject.Information.MasterDBPath
214+
}
215+
216+
# Create file group specifications for all file groups in the source database
217+
$generatedFileGroups = [System.Collections.Generic.List[DatabaseFileGroupSpec]]::new()
218+
219+
foreach ($sourceFileGroup in $sourceDatabase.FileGroups)
220+
{
221+
$fileSpecs = [System.Collections.Generic.List[DatabaseFileSpec]]::new()
222+
223+
foreach ($sourceFile in $sourceFileGroup.Files)
224+
{
225+
# Use the same physical filename as the source file, but with .ss extension
226+
$sourceFileName = [System.IO.Path]::GetFileNameWithoutExtension($sourceFile.FileName)
227+
$sparseFileName = '{0}.ss' -f $sourceFileName
228+
$sparseFilePath = Join-Path -Path $defaultDataDirectory -ChildPath $sparseFileName
229+
230+
# Create a file spec using the same logical name as the source database file
231+
$fileSpec = [DatabaseFileSpec]::new()
232+
$fileSpec.Name = $sourceFile.Name
233+
$fileSpec.FileName = $sparseFilePath
234+
235+
$fileSpecs.Add($fileSpec)
236+
}
237+
238+
# Create file group spec
239+
$fileGroupSpec = [DatabaseFileGroupSpec]::new($sourceFileGroup.Name)
240+
$fileGroupSpec.Files = $fileSpecs.ToArray()
241+
242+
$generatedFileGroups.Add($fileGroupSpec)
243+
}
244+
245+
$FileGroup = $generatedFileGroups.ToArray()
246+
}
247+
191248
# Create the snapshot using New-SqlDscDatabase
192249
$newSqlDscDatabaseParameters = @{
193250
ServerObject = $ServerObject
194251
Name = $Name
195252
DatabaseSnapshotBaseName = $DatabaseName
253+
FileGroup = $FileGroup
196254
Force = $Force
197255
WhatIf = $WhatIfPreference
198256
}
199257

200-
if ($PSBoundParameters.ContainsKey('FileGroup'))
201-
{
202-
$newSqlDscDatabaseParameters['FileGroup'] = $FileGroup
203-
}
204-
205258
if ($PSCmdlet.ParameterSetName -eq 'ServerObject' -and $Refresh.IsPresent)
206259
{
207260
$newSqlDscDatabaseParameters['Refresh'] = $true

tests/Integration/Commands/New-SqlDscDatabaseSnapshot.Integration.Tests.ps1

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,14 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag @('Integration_SQL2017', 'Integration
141141
}
142142

143143
It 'Should create a database snapshot with custom sparse file location' {
144+
# Get the logical name of the source database's primary file
145+
$sourceDatabase = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentSourceDatabase -ErrorAction 'Stop'
146+
$sourceLogicalFileName = $sourceDatabase.FileGroups['PRIMARY'].Files[0].Name
147+
144148
# Create PRIMARY filegroup with sparse file using -AsSpec
149+
# IMPORTANT: Must use the same logical name as the source database file
145150
$sparseFilePath = Join-Path -Path $script:dataDirectory -ChildPath ($script:testSnapshotNameWithFileGroup + '_Sparse.ss')
146-
$dataFileSpec = New-SqlDscDataFile -Name ($script:testSnapshotNameWithFileGroup + '_Sparse') -FileName $sparseFilePath -AsSpec
151+
$dataFileSpec = New-SqlDscDataFile -Name $sourceLogicalFileName -FileName $sparseFilePath -AsSpec
147152
$primaryFileGroupSpec = New-SqlDscFileGroup -Name 'PRIMARY' -Files @($dataFileSpec) -AsSpec
148153

149154
# Create snapshot with custom file group

tests/Unit/Public/New-SqlDscDatabaseSnapshot.Tests.ps1

Lines changed: 152 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,34 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' {
5858
$mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Enterprise Edition' -Force
5959
$mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'VersionMajor' -Value 15 -Force
6060

61-
# Mock source database
61+
# Mock Settings for default data directory
62+
$mockSettings = New-Object -TypeName 'PSObject'
63+
$mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force
64+
$mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force
65+
66+
# Mock source database with file groups and files
6267
$mockSourceDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database'
6368
$mockSourceDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'SourceDatabase' -Force
6469

65-
$mockServerObject | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value {
66-
return @{
67-
'SourceDatabase' = $mockSourceDatabase
68-
} | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value {
69-
# Mock implementation
70-
} -PassThru -Force
70+
# Mock file group and data file
71+
$mockDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile'
72+
$mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'SourceDatabase' -Force
73+
$mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/SourceDatabase.mdf' -Force
74+
75+
$mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup'
76+
$mockFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force
77+
$mockFileGroup | Add-Member -MemberType 'ScriptProperty' -Name 'Files' -Value {
78+
return @($mockDataFile)
7179
} -Force
7280

81+
$mockSourceDatabase | Add-Member -MemberType 'ScriptProperty' -Name 'FileGroups' -Value {
82+
return @($mockFileGroup)
83+
} -Force
84+
85+
Mock -CommandName 'Get-SqlDscDatabase' -MockWith {
86+
return $mockSourceDatabase
87+
}
88+
7389
Mock -CommandName 'New-SqlDscDatabase' -MockWith {
7490
$mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database'
7591
$mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force
@@ -85,6 +101,26 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' {
85101
$result.Name | Should -Be 'TestSnapshot'
86102
$result.DatabaseSnapshotBaseName | Should -Be 'SourceDatabase'
87103
Should -Invoke -CommandName 'New-SqlDscDatabase' -Exactly -Times 1
104+
Should -Invoke -CommandName 'Get-SqlDscDatabase' -Exactly -Times 1
105+
}
106+
107+
It 'Should auto-generate file groups when not specified' {
108+
$result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -Force
109+
110+
Should -Invoke -CommandName 'Get-SqlDscDatabase' -ParameterFilter {
111+
$ServerObject.InstanceName -eq 'TestInstance' -and
112+
$Name -eq 'SourceDatabase' -and
113+
$ErrorAction -eq 'Stop'
114+
} -Exactly -Times 1
115+
116+
Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter {
117+
$FileGroup -and
118+
$FileGroup.Count -eq 1 -and
119+
$FileGroup[0].Name -eq 'PRIMARY' -and
120+
$FileGroup[0].Files.Count -eq 1 -and
121+
$FileGroup[0].Files[0].Name -eq 'SourceDatabase' -and
122+
$FileGroup[0].Files[0].FileName -eq '/var/opt/mssql/data/SourceDatabase.ss'
123+
} -Exactly -Times 1
88124
}
89125

90126
It 'Should pass the correct parameters to New-SqlDscDatabase' {
@@ -98,9 +134,13 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' {
98134
} -Exactly -Times 1
99135
}
100136

101-
It 'Should pass Refresh parameter when specified' {
137+
It 'Should pass Refresh parameter to Get-SqlDscDatabase when specified' {
102138
$result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -Refresh -Force
103139

140+
Should -Invoke -CommandName 'Get-SqlDscDatabase' -ParameterFilter {
141+
$Refresh -eq $true
142+
} -Exactly -Times 1
143+
104144
Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter {
105145
$Refresh -eq $true
106146
} -Exactly -Times 1
@@ -122,10 +162,35 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' {
122162

123163
$result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObject -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -FileGroup @($mockFileGroupSpec) -Force
124164

165+
# Should not call Get-SqlDscDatabase when FileGroup is specified
166+
Should -Invoke -CommandName 'Get-SqlDscDatabase' -Exactly -Times 0 -Scope It
167+
125168
Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter {
126169
$FileGroup -and $FileGroup.Count -eq 1 -and $FileGroup[0].Name -eq 'PRIMARY'
127170
} -Exactly -Times 1
128171
}
172+
173+
It 'Should use MasterDBPath when DefaultFile is not set' {
174+
# Create a server object without DefaultFile set
175+
$mockServerObjectNoDefaultFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'
176+
$mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force
177+
$mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Enterprise' -Force
178+
$mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Enterprise Edition' -Force
179+
180+
$mockSettingsNoDefault = New-Object -TypeName 'PSObject'
181+
$mockSettingsNoDefault | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value $null -Force
182+
$mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettingsNoDefault -Force
183+
184+
$mockInformation = New-Object -TypeName 'PSObject'
185+
$mockInformation | Add-Member -MemberType 'NoteProperty' -Name 'MasterDBPath' -Value '/var/opt/mssql/master' -Force
186+
$mockServerObjectNoDefaultFile | Add-Member -MemberType 'NoteProperty' -Name 'Information' -Value $mockInformation -Force
187+
188+
$result = New-SqlDscDatabaseSnapshot -ServerObject $mockServerObjectNoDefaultFile -Name 'TestSnapshot' -DatabaseName 'SourceDatabase' -Force
189+
190+
Should -Invoke -CommandName 'New-SqlDscDatabase' -ParameterFilter {
191+
$FileGroup[0].Files[0].FileName -eq '/var/opt/mssql/master/SourceDatabase.ss'
192+
} -Exactly -Times 1
193+
}
129194
}
130195

131196
Context 'When creating a database snapshot using DatabaseObject parameter set' {
@@ -135,10 +200,34 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' {
135200
$mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Enterprise' -Force
136201
$mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Enterprise Edition' -Force
137202

203+
# Mock Settings for default data directory
204+
$mockSettings = New-Object -TypeName 'PSObject'
205+
$mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force
206+
$mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force
207+
138208
$mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database'
139209
$mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force
140210
$mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Parent' -Value $mockServerObject -Force
141211

212+
# Mock file group and data file for DatabaseObject
213+
$mockDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile'
214+
$mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force
215+
$mockDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/MyDatabase.mdf' -Force
216+
217+
$mockFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup'
218+
$mockFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force
219+
$mockFileGroup | Add-Member -MemberType 'ScriptProperty' -Name 'Files' -Value {
220+
return @($mockDataFile)
221+
} -Force
222+
223+
$mockDatabaseObject | Add-Member -MemberType 'ScriptProperty' -Name 'FileGroups' -Value {
224+
return @($mockFileGroup)
225+
} -Force
226+
227+
Mock -CommandName 'Get-SqlDscDatabase' -MockWith {
228+
return $mockDatabaseObject
229+
}
230+
142231
Mock -CommandName 'New-SqlDscDatabase' -MockWith {
143232
$mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database'
144233
$mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force
@@ -154,6 +243,7 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' {
154243
$result.Name | Should -Be 'TestSnapshot'
155244
$result.DatabaseSnapshotBaseName | Should -Be 'MyDatabase'
156245
Should -Invoke -CommandName 'New-SqlDscDatabase' -Exactly -Times 1
246+
Should -Invoke -CommandName 'Get-SqlDscDatabase' -Exactly -Times 1
157247
}
158248

159249
It 'Should pass the correct parameters to New-SqlDscDatabase from DatabaseObject' {
@@ -211,6 +301,33 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' {
211301
$mockServerObjectDeveloper | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Standard' -Force
212302
$mockServerObjectDeveloper | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Developer Edition' -Force
213303

304+
# Mock Settings for default data directory
305+
$mockSettings = New-Object -TypeName 'PSObject'
306+
$mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force
307+
$mockServerObjectDeveloper | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force
308+
309+
# Mock source database
310+
$mockDevSourceDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database'
311+
$mockDevSourceDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force
312+
313+
$mockDevDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile'
314+
$mockDevDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force
315+
$mockDevDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/MyDatabase.mdf' -Force
316+
317+
$mockDevFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup'
318+
$mockDevFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force
319+
$mockDevFileGroup | Add-Member -MemberType 'ScriptProperty' -Name 'Files' -Value {
320+
return @($mockDevDataFile)
321+
} -Force
322+
323+
$mockDevSourceDatabase | Add-Member -MemberType 'ScriptProperty' -Name 'FileGroups' -Value {
324+
return @($mockDevFileGroup)
325+
} -Force
326+
327+
Mock -CommandName 'Get-SqlDscDatabase' -MockWith {
328+
return $mockDevSourceDatabase
329+
}
330+
214331
Mock -CommandName 'New-SqlDscDatabase' -MockWith {
215332
$mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database'
216333
$mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force
@@ -235,6 +352,33 @@ Describe 'New-SqlDscDatabaseSnapshot' -Tag 'Public' {
235352
$mockServerObjectEvaluation | Add-Member -MemberType 'NoteProperty' -Name 'EngineEdition' -Value 'Standard' -Force
236353
$mockServerObjectEvaluation | Add-Member -MemberType 'NoteProperty' -Name 'Edition' -Value 'Evaluation Edition' -Force
237354

355+
# Mock Settings for default data directory
356+
$mockSettings = New-Object -TypeName 'PSObject'
357+
$mockSettings | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFile' -Value '/var/opt/mssql/data' -Force
358+
$mockServerObjectEvaluation | Add-Member -MemberType 'NoteProperty' -Name 'Settings' -Value $mockSettings -Force
359+
360+
# Mock source database
361+
$mockEvalSourceDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database'
362+
$mockEvalSourceDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force
363+
364+
$mockEvalDataFile = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DataFile'
365+
$mockEvalDataFile | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyDatabase' -Force
366+
$mockEvalDataFile | Add-Member -MemberType 'NoteProperty' -Name 'FileName' -Value '/var/opt/mssql/data/MyDatabase.mdf' -Force
367+
368+
$mockEvalFileGroup = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.FileGroup'
369+
$mockEvalFileGroup | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'PRIMARY' -Force
370+
$mockEvalFileGroup | Add-Member -MemberType 'ScriptProperty' -Name 'Files' -Value {
371+
return @($mockEvalDataFile)
372+
} -Force
373+
374+
$mockEvalSourceDatabase | Add-Member -MemberType 'ScriptProperty' -Name 'FileGroups' -Value {
375+
return @($mockEvalFileGroup)
376+
} -Force
377+
378+
Mock -CommandName 'Get-SqlDscDatabase' -MockWith {
379+
return $mockEvalSourceDatabase
380+
}
381+
238382
Mock -CommandName 'New-SqlDscDatabase' -MockWith {
239383
$mockSnapshotDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database'
240384
$mockSnapshotDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value $Name -Force

0 commit comments

Comments
 (0)