Skip to content

Commit 03e9f51

Browse files
authored
Save-SqlDscSqlServerMediaFile: Fix handling with existing ISO files (#2299)
1 parent e32d6ba commit 03e9f51

File tree

5 files changed

+139
-48
lines changed

5 files changed

+139
-48
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
137137

138138
### Fixed
139139

140+
- `Save-SqlDscSqlServerMediaFile`
141+
- Fixed the Force parameter to work correctly when the target ISO file already
142+
exists. The command now properly overwrites the target file when Force is
143+
specified. Removed the safety check that was incorrectly blocking execution
144+
when other ISO files existed in the destination directory
145+
([issue #2280](https://github.com/dsccommunity/SqlServerDsc/issues/2280)).
140146
- `DSC_SqlRS`
141147
- Fixed intermittent initialization failures on resource-constrained systems
142148
(particularly Windows Server 2025 in CI) by adding an optional `RestartTimeout`

source/Public/Save-SqlDscSqlServerMediaFile.ps1

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,9 @@ function Save-SqlDscSqlServerMediaFile
107107
$ConfirmPreference = 'None'
108108
}
109109

110-
if ((Get-Item -Path "$DestinationPath/*.iso" -Force).Count -gt 0 -and -not $SkipExecution)
111-
{
112-
$auditAlreadyPresentMessage = $script:localizedData.SqlServerMediaFile_Save_InvalidDestinationFolder
113-
114-
$PSCmdlet.ThrowTerminatingError(
115-
[System.Management.Automation.ErrorRecord]::new(
116-
$auditAlreadyPresentMessage,
117-
'SSDSSM0001', # cspell: disable-line
118-
[System.Management.Automation.ErrorCategory]::InvalidOperation,
119-
$DestinationPath
120-
)
121-
)
122-
}
123-
124110
$destinationFilePath = Join-Path -Path $DestinationPath -ChildPath $FileName
125111

112+
# Handle target file if it exists - Force parameter controls overwrite behavior
126113
if ((Test-Path -Path $destinationFilePath))
127114
{
128115
$verboseDescriptionMessage = $script:localizedData.SqlServerMediaFile_Save_ShouldProcessVerboseDescription -f $destinationFilePath
@@ -226,10 +213,11 @@ function Save-SqlDscSqlServerMediaFile
226213
Message = $script:localizedData.SqlServerMediaFile_Save_MultipleFilesFoundAfterDownload
227214
Category = 'InvalidOperation'
228215
ErrorId = 'SSDSSM0002' # CSpell: disable-line
229-
TargetObject = $ServiceType
216+
TargetObject = $DestinationPath
230217
}
231218

232219
Write-Error @writeErrorParameters
220+
return
233221
}
234222

235223
Write-Verbose -Message ($script:localizedData.SqlServerMediaFile_Save_RenamingFile -f $isoFile.Name, $FileName)

source/en-US/SqlServerDsc.strings.psd1

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ ConvertFrom-StringData @'
274274
SqlServerMediaFile_Save_ShouldProcessVerboseWarning = Are you sure you want to replace existing file '{0}'?
275275
# This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages.
276276
SqlServerMediaFile_Save_ShouldProcessCaption = Replace existing file
277-
SqlServerMediaFile_Save_InvalidDestinationFolder = Multiple files with the .iso extension was found in the destination path. Please choose another destination folder.
278277
SqlServerMediaFile_Save_MultipleFilesFoundAfterDownload = Multiple files with the .iso extension was found in the destination path. Cannot determine which one of the files that was downloaded.
279278
SqlServerMediaFile_Save_DownloadingInformation = Downloading the SQL Server media from '{0}'.
280279
SqlServerMediaFile_Save_IsExecutable = Downloaded an executable file. Using the executable to download the media file.

tests/Integration/Commands/Save-SqlDscSqlServerMediaFile.Integration.Tests.ps1

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
3434
# Create a temporary directory for testing downloads
3535
$script:testDownloadPath = Join-Path -Path $env:TEMP -ChildPath "SqlDscTestDownloads_$(Get-Random)"
3636
New-Item -Path $script:testDownloadPath -ItemType Directory -Force | Out-Null
37-
37+
3838
Write-Verbose -Message "Created test download directory: $script:testDownloadPath" -Verbose
3939
}
4040

@@ -52,7 +52,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
5252
# Use SQL Server 2017 ISO URL for testing direct ISO download
5353
$script:directIsoUrl = 'https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SQLServer2017-x64-ENU.iso'
5454
$script:expectedFileName = 'SQLServer2017-test.iso'
55-
55+
5656
# Create separate subdirectory for this context to avoid ISO file conflicts
5757
$script:directIsoTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'DirectIso'
5858
New-Item -Path $script:directIsoTestPath -ItemType Directory -Force | Out-Null
@@ -63,12 +63,12 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
6363

6464
# Verify the result is a FileInfo object
6565
$result | Should -BeOfType [System.IO.FileInfo]
66-
66+
6767
# Verify the file was downloaded
6868
$result.Name | Should -Be $script:expectedFileName
6969
$result.Exists | Should -BeTrue
7070
$result.Length | Should -BeGreaterThan 0
71-
71+
7272
# Verify the file is in the expected location
7373
$expectedPath = Join-Path -Path $script:directIsoTestPath -ChildPath $script:expectedFileName
7474
Test-Path -Path $expectedPath | Should -BeTrue
@@ -78,22 +78,22 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
7878
# Create separate subdirectory for this test to avoid conflicts
7979
$overwriteTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'OverwriteTest'
8080
New-Item -Path $overwriteTestPath -ItemType Directory -Force | Out-Null
81-
82-
# Use SkipExecution to avoid the safety check while still testing Force parameter
81+
82+
# Use SkipExecution to download only the executable without extracting ISO
8383
$executableUrl = 'https://download.microsoft.com/download/e/6/4/e6477a2a-9b58-40f7-8ad6-62bb8491ea78/SQLServerReportingServices.exe'
8484
$targetFileName = 'overwrite-test.exe'
85-
85+
8686
# First, create a dummy file with the target name
8787
$dummyFilePath = Join-Path -Path $overwriteTestPath -ChildPath $targetFileName
8888
'dummy content for overwrite test' | Out-File -FilePath $dummyFilePath -Encoding UTF8
89-
89+
9090
# Verify the dummy file exists and get its original size
9191
Test-Path -Path $dummyFilePath | Should -BeTrue
9292
$originalSize = (Get-Item -Path $dummyFilePath).Length
93-
93+
9494
# Download with SkipExecution and Force should overwrite the dummy file
9595
$result = Save-SqlDscSqlServerMediaFile -Url $executableUrl -DestinationPath $overwriteTestPath -FileName $targetFileName -SkipExecution -Force -Quiet -ErrorAction 'Stop'
96-
96+
9797
# Verify the file was overwritten (should be much larger than the dummy content)
9898
$result | Should -BeOfType [System.IO.FileInfo]
9999
$result.Name | Should -Be $targetFileName
@@ -108,7 +108,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
108108
# Use SQL Server 2022 executable URL for testing executable download and extraction
109109
$script:executableUrl = 'https://download.microsoft.com/download/c/c/9/cc9c6797-383c-4b24-8920-dc057c1de9d3/SQL2022-SSEI-Dev.exe'
110110
$script:expectedIsoFileName = 'SQL2022-media.iso'
111-
111+
112112
# Create separate subdirectory for this context to avoid ISO file conflicts
113113
$script:executableTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'ExecutableTest'
114114
New-Item -Path $script:executableTestPath -ItemType Directory -Force | Out-Null
@@ -128,7 +128,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
128128
# Verify the executable was cleaned up (should not exist)
129129
$executablePath = [System.IO.Path]::ChangeExtension($result.FullName, 'exe')
130130
Test-Path -Path $executablePath | Should -BeFalse
131-
131+
132132
# Verify only one ISO file exists in the directory
133133
$isoFiles = Get-ChildItem -Path $script:executableTestPath -Filter '*.iso'
134134
$isoFiles.Count | Should -Be 1
@@ -140,7 +140,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
140140
# Use SQL Server Reporting Services executable for testing SkipExecution
141141
$script:rsExecutableUrl = 'https://download.microsoft.com/download/e/6/4/e6477a2a-9b58-40f7-8ad6-62bb8491ea78/SQLServerReportingServices.exe'
142142
$script:expectedExecutableFileName = 'SSRS-Test.exe'
143-
143+
144144
# Create separate subdirectory for this context to avoid file conflicts
145145
$script:skipExecutionTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'SkipExecutionTest'
146146
New-Item -Path $script:skipExecutionTestPath -ItemType Directory -Force | Out-Null
@@ -168,19 +168,63 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
168168
$script:errorTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'ErrorTest'
169169
New-Item -Path $script:errorTestPath -ItemType Directory -Force | Out-Null
170170
}
171-
172-
It 'Should throw error when ISO files already exist in destination and SkipExecution is not used' {
173-
# Create a dummy ISO file to trigger the error condition
174-
$dummyIsoPath = Join-Path -Path $script:errorTestPath -ChildPath 'existing.iso'
175-
'dummy iso content' | Out-File -FilePath $dummyIsoPath -Encoding UTF8
176171

177-
# This should throw an error due to existing ISO file
178-
{
179-
Save-SqlDscSqlServerMediaFile -Url $script:directIsoUrl -DestinationPath $script:errorTestPath -FileName 'new-download.iso' -Quiet -ErrorAction 'Stop'
180-
} | Should -Throw
172+
It 'Should download successfully even when multiple different ISO files already exist in destination' {
173+
# Create two different dummy ISO files in the destination directory
174+
$dummyIso1Path = Join-Path -Path $script:errorTestPath -ChildPath 'existing1.iso'
175+
$dummyIso2Path = Join-Path -Path $script:errorTestPath -ChildPath 'existing2.iso'
176+
'dummy iso content 1' | Out-File -FilePath $dummyIso1Path -Encoding UTF8
177+
'dummy iso content 2' | Out-File -FilePath $dummyIso2Path -Encoding UTF8
178+
179+
# Verify the dummy ISO files exist
180+
Test-Path -Path $dummyIso1Path | Should -BeTrue
181+
Test-Path -Path $dummyIso2Path | Should -BeTrue
182+
183+
# This should succeed - the command no longer blocks on non-target ISO files
184+
$targetFileName = 'new-download.iso'
185+
$result = Save-SqlDscSqlServerMediaFile -Url $script:directIsoUrl -DestinationPath $script:errorTestPath -FileName $targetFileName -Force -Quiet -ErrorAction 'Stop'
186+
187+
# Verify the download succeeded
188+
$result | Should -BeOfType [System.IO.FileInfo]
189+
$result.Name | Should -Be $targetFileName
190+
$result.Exists | Should -BeTrue
191+
192+
# Verify the other ISO files still exist (they should not be affected)
193+
Test-Path -Path $dummyIso1Path | Should -BeTrue
194+
Test-Path -Path $dummyIso2Path | Should -BeTrue
195+
196+
# Clean up
197+
Remove-Item -Path $dummyIso1Path -Force -ErrorAction SilentlyContinue
198+
Remove-Item -Path $dummyIso2Path -Force -ErrorAction SilentlyContinue
199+
Remove-Item -Path (Join-Path -Path $script:errorTestPath -ChildPath $targetFileName) -Force -ErrorAction SilentlyContinue
200+
}
201+
202+
It 'Should allow overwriting the same ISO file when Force parameter is used' {
203+
# Create a separate subdirectory for this test
204+
$forceOverwriteTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'ForceOverwriteIso'
205+
New-Item -Path $forceOverwriteTestPath -ItemType Directory -Force | Out-Null
206+
207+
# First, create a dummy ISO file with the exact same name we'll download
208+
$targetFileName = 'force-overwrite-test.iso'
209+
$dummyIsoPath = Join-Path -Path $forceOverwriteTestPath -ChildPath $targetFileName
210+
'dummy iso content for force overwrite test' | Out-File -FilePath $dummyIsoPath -Encoding UTF8
211+
212+
# Verify the dummy file exists and get its original size
213+
Test-Path -Path $dummyIsoPath | Should -BeTrue
214+
$originalSize = (Get-Item -Path $dummyIsoPath).Length
215+
216+
# Download with Force parameter should overwrite the existing ISO file
217+
$result = Save-SqlDscSqlServerMediaFile -Url $script:directIsoUrl -DestinationPath $forceOverwriteTestPath -FileName $targetFileName -Force -Quiet -ErrorAction 'Stop'
218+
219+
# Verify the file was overwritten (should be much larger than the dummy content)
220+
$result | Should -BeOfType [System.IO.FileInfo]
221+
$result.Name | Should -Be $targetFileName
222+
$result.Exists | Should -BeTrue
223+
$result.Length | Should -BeGreaterThan $originalSize
224+
$result.Length | Should -BeGreaterThan 1000000 # Real ISO should be at least 1MB
181225

182226
# Clean up
183-
Remove-Item -Path $dummyIsoPath -Force -ErrorAction SilentlyContinue
227+
Remove-Item -Path $forceOverwriteTestPath -Recurse -Force -ErrorAction SilentlyContinue
184228
}
185229

186230
It 'Should handle invalid URL gracefully' {
@@ -195,7 +239,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
195239
BeforeAll {
196240
# Use SQL Server 2019 executable for language testing
197241
$script:sql2019Url = 'https://download.microsoft.com/download/d/a/2/da259851-b941-459d-989c-54a18a5d44dd/SQL2019-SSEI-Dev.exe'
198-
242+
199243
# Create separate subdirectory for language testing to avoid conflicts
200244
$script:languageTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'LanguageTest'
201245
New-Item -Path $script:languageTestPath -ItemType Directory -Force | Out-Null

tests/Unit/Public/Save-SqlDscSqlServerMediaFile.Tests.ps1

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -200,21 +200,75 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag 'Public' {
200200
}
201201
}
202202

203-
Context 'When there is already an ISO file in the destination path' {
203+
Context 'When the same ISO file already exists and Force parameter is used' {
204204
BeforeAll {
205205
Mock -CommandName Get-Item -MockWith {
206-
return @{
207-
Count = 1
208-
}
206+
return @(
207+
@{
208+
FullName = Join-Path -Path $DestinationPath -ChildPath 'media.iso'
209+
Count = 1
210+
}
211+
)
212+
} -ParameterFilter {
213+
$Path -eq "$DestinationPath/*.iso"
209214
}
215+
216+
Mock -CommandName Test-Path -MockWith {
217+
return $true
218+
} -ParameterFilter {
219+
$Path -eq (Join-Path -Path $DestinationPath -ChildPath 'media.iso')
220+
}
221+
222+
Mock -CommandName Invoke-WebRequest
223+
Mock -CommandName Remove-Item
210224
}
211225

212-
It 'Should throw the correct error' {
213-
$mockErrorMessage = InModuleScope -ScriptBlock {
214-
$script:localizedData.SqlServerMediaFile_Save_InvalidDestinationFolder
226+
It 'Should allow overwriting the existing file with Force parameter' {
227+
Save-SqlDscSqlServerMediaFile -Url 'https://example.com/media.iso' -DestinationPath $DestinationPath -Force
228+
229+
Should -Invoke -CommandName Invoke-WebRequest -Exactly -Times 1 -Scope It
230+
Should -Invoke -CommandName Remove-Item -Exactly -Times 1 -Scope It
231+
}
232+
}
233+
234+
Context 'When URL is an .exe and the same ISO file already exists with Force parameter' {
235+
BeforeAll {
236+
$exeUrl = 'https://example.com/installer.exe'
237+
238+
Mock -CommandName Get-Item -MockWith {
239+
return @(
240+
@{
241+
FullName = Join-Path -Path $DestinationPath -ChildPath 'media.iso'
242+
Count = 1
243+
}
244+
)
245+
} -ParameterFilter {
246+
$Path -eq "$DestinationPath/*.iso"
247+
}
248+
249+
Mock -CommandName Test-Path -MockWith {
250+
return $true
251+
} -ParameterFilter {
252+
$Path -eq (Join-Path -Path $DestinationPath -ChildPath 'media.iso')
215253
}
216254

217-
{ Save-SqlDscSqlServerMediaFile -Url $Url -DestinationPath $DestinationPath -Confirm:$false } | Should -Throw $mockErrorMessage
255+
Mock -CommandName Invoke-WebRequest
256+
Mock -CommandName Start-Process
257+
Mock -CommandName Remove-Item
258+
Mock -CommandName Rename-Item
259+
}
260+
261+
It 'Should allow overwriting the existing file with Force parameter' {
262+
Save-SqlDscSqlServerMediaFile -Url $exeUrl -DestinationPath $DestinationPath -Force
263+
264+
Should -Invoke -CommandName Invoke-WebRequest -Exactly -Times 1 -Scope It
265+
Should -Invoke -CommandName Remove-Item -ParameterFilter {
266+
$Path -eq (Join-Path -Path $DestinationPath -ChildPath 'media.iso')
267+
} -Exactly -Times 1 -Scope It
268+
Should -Invoke -CommandName Start-Process -Exactly -Times 1 -Scope It
269+
Should -Invoke -CommandName Remove-Item -ParameterFilter {
270+
$Path -eq (Join-Path -Path $DestinationPath -ChildPath 'media.exe')
271+
} -Exactly -Times 1 -Scope It
218272
}
219273
}
220274
}

0 commit comments

Comments
 (0)