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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- `Save-SqlDscSqlServerMediaFile`
- Fixed the Force parameter to work correctly when the target ISO file already
exists. The command now properly overwrites the target file when Force is
specified. Removed the safety check that was incorrectly blocking execution
when other ISO files existed in the destination directory
([issue #2280](https://github.com/dsccommunity/SqlServerDsc/issues/2280)).
- `DSC_SqlRS`
- Fixed intermittent initialization failures on resource-constrained systems
(particularly Windows Server 2025 in CI) by adding an optional `RestartTimeout`
Expand Down
18 changes: 3 additions & 15 deletions source/Public/Save-SqlDscSqlServerMediaFile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -107,22 +107,9 @@ function Save-SqlDscSqlServerMediaFile
$ConfirmPreference = 'None'
}

if ((Get-Item -Path "$DestinationPath/*.iso" -Force).Count -gt 0 -and -not $SkipExecution)
{
$auditAlreadyPresentMessage = $script:localizedData.SqlServerMediaFile_Save_InvalidDestinationFolder

$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
$auditAlreadyPresentMessage,
'SSDSSM0001', # cspell: disable-line
[System.Management.Automation.ErrorCategory]::InvalidOperation,
$DestinationPath
)
)
}

$destinationFilePath = Join-Path -Path $DestinationPath -ChildPath $FileName

# Handle target file if it exists - Force parameter controls overwrite behavior
if ((Test-Path -Path $destinationFilePath))
{
$verboseDescriptionMessage = $script:localizedData.SqlServerMediaFile_Save_ShouldProcessVerboseDescription -f $destinationFilePath
Expand Down Expand Up @@ -226,10 +213,11 @@ function Save-SqlDscSqlServerMediaFile
Message = $script:localizedData.SqlServerMediaFile_Save_MultipleFilesFoundAfterDownload
Category = 'InvalidOperation'
ErrorId = 'SSDSSM0002' # CSpell: disable-line
TargetObject = $ServiceType
TargetObject = $DestinationPath
}

Write-Error @writeErrorParameters
return
}

Write-Verbose -Message ($script:localizedData.SqlServerMediaFile_Save_RenamingFile -f $isoFile.Name, $FileName)
Expand Down
1 change: 0 additions & 1 deletion source/en-US/SqlServerDsc.strings.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,6 @@ ConvertFrom-StringData @'
SqlServerMediaFile_Save_ShouldProcessVerboseWarning = Are you sure you want to replace existing file '{0}'?
# This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages.
SqlServerMediaFile_Save_ShouldProcessCaption = Replace existing file
SqlServerMediaFile_Save_InvalidDestinationFolder = Multiple files with the .iso extension was found in the destination path. Please choose another destination folder.
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.
SqlServerMediaFile_Save_DownloadingInformation = Downloading the SQL Server media from '{0}'.
SqlServerMediaFile_Save_IsExecutable = Downloaded an executable file. Using the executable to download the media file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
# Create a temporary directory for testing downloads
$script:testDownloadPath = Join-Path -Path $env:TEMP -ChildPath "SqlDscTestDownloads_$(Get-Random)"
New-Item -Path $script:testDownloadPath -ItemType Directory -Force | Out-Null

Write-Verbose -Message "Created test download directory: $script:testDownloadPath" -Verbose
}

Expand All @@ -52,7 +52,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
# Use SQL Server 2017 ISO URL for testing direct ISO download
$script:directIsoUrl = 'https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SQLServer2017-x64-ENU.iso'
$script:expectedFileName = 'SQLServer2017-test.iso'

# Create separate subdirectory for this context to avoid ISO file conflicts
$script:directIsoTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'DirectIso'
New-Item -Path $script:directIsoTestPath -ItemType Directory -Force | Out-Null
Expand All @@ -63,12 +63,12 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat

# Verify the result is a FileInfo object
$result | Should -BeOfType [System.IO.FileInfo]

# Verify the file was downloaded
$result.Name | Should -Be $script:expectedFileName
$result.Exists | Should -BeTrue
$result.Length | Should -BeGreaterThan 0

# Verify the file is in the expected location
$expectedPath = Join-Path -Path $script:directIsoTestPath -ChildPath $script:expectedFileName
Test-Path -Path $expectedPath | Should -BeTrue
Expand All @@ -78,22 +78,22 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
# Create separate subdirectory for this test to avoid conflicts
$overwriteTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'OverwriteTest'
New-Item -Path $overwriteTestPath -ItemType Directory -Force | Out-Null
# Use SkipExecution to avoid the safety check while still testing Force parameter

# Use SkipExecution to download only the executable without extracting ISO
$executableUrl = 'https://download.microsoft.com/download/e/6/4/e6477a2a-9b58-40f7-8ad6-62bb8491ea78/SQLServerReportingServices.exe'
$targetFileName = 'overwrite-test.exe'

# First, create a dummy file with the target name
$dummyFilePath = Join-Path -Path $overwriteTestPath -ChildPath $targetFileName
'dummy content for overwrite test' | Out-File -FilePath $dummyFilePath -Encoding UTF8

# Verify the dummy file exists and get its original size
Test-Path -Path $dummyFilePath | Should -BeTrue
$originalSize = (Get-Item -Path $dummyFilePath).Length

# Download with SkipExecution and Force should overwrite the dummy file
$result = Save-SqlDscSqlServerMediaFile -Url $executableUrl -DestinationPath $overwriteTestPath -FileName $targetFileName -SkipExecution -Force -Quiet -ErrorAction 'Stop'

# Verify the file was overwritten (should be much larger than the dummy content)
$result | Should -BeOfType [System.IO.FileInfo]
$result.Name | Should -Be $targetFileName
Expand All @@ -108,7 +108,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
# Use SQL Server 2022 executable URL for testing executable download and extraction
$script:executableUrl = 'https://download.microsoft.com/download/c/c/9/cc9c6797-383c-4b24-8920-dc057c1de9d3/SQL2022-SSEI-Dev.exe'
$script:expectedIsoFileName = 'SQL2022-media.iso'

# Create separate subdirectory for this context to avoid ISO file conflicts
$script:executableTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'ExecutableTest'
New-Item -Path $script:executableTestPath -ItemType Directory -Force | Out-Null
Expand All @@ -128,7 +128,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
# Verify the executable was cleaned up (should not exist)
$executablePath = [System.IO.Path]::ChangeExtension($result.FullName, 'exe')
Test-Path -Path $executablePath | Should -BeFalse

# Verify only one ISO file exists in the directory
$isoFiles = Get-ChildItem -Path $script:executableTestPath -Filter '*.iso'
$isoFiles.Count | Should -Be 1
Expand All @@ -140,7 +140,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
# Use SQL Server Reporting Services executable for testing SkipExecution
$script:rsExecutableUrl = 'https://download.microsoft.com/download/e/6/4/e6477a2a-9b58-40f7-8ad6-62bb8491ea78/SQLServerReportingServices.exe'
$script:expectedExecutableFileName = 'SSRS-Test.exe'

# Create separate subdirectory for this context to avoid file conflicts
$script:skipExecutionTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'SkipExecutionTest'
New-Item -Path $script:skipExecutionTestPath -ItemType Directory -Force | Out-Null
Expand Down Expand Up @@ -168,19 +168,63 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
$script:errorTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'ErrorTest'
New-Item -Path $script:errorTestPath -ItemType Directory -Force | Out-Null
}

It 'Should throw error when ISO files already exist in destination and SkipExecution is not used' {
# Create a dummy ISO file to trigger the error condition
$dummyIsoPath = Join-Path -Path $script:errorTestPath -ChildPath 'existing.iso'
'dummy iso content' | Out-File -FilePath $dummyIsoPath -Encoding UTF8

# This should throw an error due to existing ISO file
{
Save-SqlDscSqlServerMediaFile -Url $script:directIsoUrl -DestinationPath $script:errorTestPath -FileName 'new-download.iso' -Quiet -ErrorAction 'Stop'
} | Should -Throw
It 'Should download successfully even when multiple different ISO files already exist in destination' {
# Create two different dummy ISO files in the destination directory
$dummyIso1Path = Join-Path -Path $script:errorTestPath -ChildPath 'existing1.iso'
$dummyIso2Path = Join-Path -Path $script:errorTestPath -ChildPath 'existing2.iso'
'dummy iso content 1' | Out-File -FilePath $dummyIso1Path -Encoding UTF8
'dummy iso content 2' | Out-File -FilePath $dummyIso2Path -Encoding UTF8

# Verify the dummy ISO files exist
Test-Path -Path $dummyIso1Path | Should -BeTrue
Test-Path -Path $dummyIso2Path | Should -BeTrue

# This should succeed - the command no longer blocks on non-target ISO files
$targetFileName = 'new-download.iso'
$result = Save-SqlDscSqlServerMediaFile -Url $script:directIsoUrl -DestinationPath $script:errorTestPath -FileName $targetFileName -Force -Quiet -ErrorAction 'Stop'

# Verify the download succeeded
$result | Should -BeOfType [System.IO.FileInfo]
$result.Name | Should -Be $targetFileName
$result.Exists | Should -BeTrue

# Verify the other ISO files still exist (they should not be affected)
Test-Path -Path $dummyIso1Path | Should -BeTrue
Test-Path -Path $dummyIso2Path | Should -BeTrue

# Clean up
Remove-Item -Path $dummyIso1Path -Force -ErrorAction SilentlyContinue
Remove-Item -Path $dummyIso2Path -Force -ErrorAction SilentlyContinue
Remove-Item -Path (Join-Path -Path $script:errorTestPath -ChildPath $targetFileName) -Force -ErrorAction SilentlyContinue
}

It 'Should allow overwriting the same ISO file when Force parameter is used' {
# Create a separate subdirectory for this test
$forceOverwriteTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'ForceOverwriteIso'
New-Item -Path $forceOverwriteTestPath -ItemType Directory -Force | Out-Null

# First, create a dummy ISO file with the exact same name we'll download
$targetFileName = 'force-overwrite-test.iso'
$dummyIsoPath = Join-Path -Path $forceOverwriteTestPath -ChildPath $targetFileName
'dummy iso content for force overwrite test' | Out-File -FilePath $dummyIsoPath -Encoding UTF8

# Verify the dummy file exists and get its original size
Test-Path -Path $dummyIsoPath | Should -BeTrue
$originalSize = (Get-Item -Path $dummyIsoPath).Length

# Download with Force parameter should overwrite the existing ISO file
$result = Save-SqlDscSqlServerMediaFile -Url $script:directIsoUrl -DestinationPath $forceOverwriteTestPath -FileName $targetFileName -Force -Quiet -ErrorAction 'Stop'

# Verify the file was overwritten (should be much larger than the dummy content)
$result | Should -BeOfType [System.IO.FileInfo]
$result.Name | Should -Be $targetFileName
$result.Exists | Should -BeTrue
$result.Length | Should -BeGreaterThan $originalSize
$result.Length | Should -BeGreaterThan 1000000 # Real ISO should be at least 1MB

# Clean up
Remove-Item -Path $dummyIsoPath -Force -ErrorAction SilentlyContinue
Remove-Item -Path $forceOverwriteTestPath -Recurse -Force -ErrorAction SilentlyContinue
}

It 'Should handle invalid URL gracefully' {
Expand All @@ -195,7 +239,7 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag @('Integration_SQL2017', 'Integrat
BeforeAll {
# Use SQL Server 2019 executable for language testing
$script:sql2019Url = 'https://download.microsoft.com/download/d/a/2/da259851-b941-459d-989c-54a18a5d44dd/SQL2019-SSEI-Dev.exe'

# Create separate subdirectory for language testing to avoid conflicts
$script:languageTestPath = Join-Path -Path $script:testDownloadPath -ChildPath 'LanguageTest'
New-Item -Path $script:languageTestPath -ItemType Directory -Force | Out-Null
Expand Down
70 changes: 62 additions & 8 deletions tests/Unit/Public/Save-SqlDscSqlServerMediaFile.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -200,21 +200,75 @@ Describe 'Save-SqlDscSqlServerMediaFile' -Tag 'Public' {
}
}

Context 'When there is already an ISO file in the destination path' {
Context 'When the same ISO file already exists and Force parameter is used' {
BeforeAll {
Mock -CommandName Get-Item -MockWith {
return @{
Count = 1
}
return @(
@{
FullName = Join-Path -Path $DestinationPath -ChildPath 'media.iso'
Count = 1
}
)
} -ParameterFilter {
$Path -eq "$DestinationPath/*.iso"
}

Mock -CommandName Test-Path -MockWith {
return $true
} -ParameterFilter {
$Path -eq (Join-Path -Path $DestinationPath -ChildPath 'media.iso')
}

Mock -CommandName Invoke-WebRequest
Mock -CommandName Remove-Item
}

It 'Should throw the correct error' {
$mockErrorMessage = InModuleScope -ScriptBlock {
$script:localizedData.SqlServerMediaFile_Save_InvalidDestinationFolder
It 'Should allow overwriting the existing file with Force parameter' {
Save-SqlDscSqlServerMediaFile -Url 'https://example.com/media.iso' -DestinationPath $DestinationPath -Force

Should -Invoke -CommandName Invoke-WebRequest -Exactly -Times 1 -Scope It
Should -Invoke -CommandName Remove-Item -Exactly -Times 1 -Scope It
}
}

Context 'When URL is an .exe and the same ISO file already exists with Force parameter' {
BeforeAll {
$exeUrl = 'https://example.com/installer.exe'

Mock -CommandName Get-Item -MockWith {
return @(
@{
FullName = Join-Path -Path $DestinationPath -ChildPath 'media.iso'
Count = 1
}
)
} -ParameterFilter {
$Path -eq "$DestinationPath/*.iso"
}

Mock -CommandName Test-Path -MockWith {
return $true
} -ParameterFilter {
$Path -eq (Join-Path -Path $DestinationPath -ChildPath 'media.iso')
}

{ Save-SqlDscSqlServerMediaFile -Url $Url -DestinationPath $DestinationPath -Confirm:$false } | Should -Throw $mockErrorMessage
Mock -CommandName Invoke-WebRequest
Mock -CommandName Start-Process
Mock -CommandName Remove-Item
Mock -CommandName Rename-Item
}

It 'Should allow overwriting the existing file with Force parameter' {
Save-SqlDscSqlServerMediaFile -Url $exeUrl -DestinationPath $DestinationPath -Force

Should -Invoke -CommandName Invoke-WebRequest -Exactly -Times 1 -Scope It
Should -Invoke -CommandName Remove-Item -ParameterFilter {
$Path -eq (Join-Path -Path $DestinationPath -ChildPath 'media.iso')
} -Exactly -Times 1 -Scope It
Should -Invoke -CommandName Start-Process -Exactly -Times 1 -Scope It
Should -Invoke -CommandName Remove-Item -ParameterFilter {
$Path -eq (Join-Path -Path $DestinationPath -ChildPath 'media.exe')
} -Exactly -Times 1 -Scope It
}
}
}
Loading