diff --git a/PSDepend/PSDependScripts/FileDownload.ps1 b/PSDepend/PSDependScripts/FileDownload.ps1 index a413954..b4e8ea5 100644 --- a/PSDepend/PSDependScripts/FileDownload.ps1 +++ b/PSDepend/PSDependScripts/FileDownload.ps1 @@ -77,10 +77,13 @@ Write-Verbose "Using URL: $URL" # Act on target path.... $ToInstall = $False # Anti pattern -# Normalize relative paths against $PWD so callers don't get burned by cwd-dependent splits -if (-not [IO.Path]::IsPathRooted($Target)) { - $Target = Join-Path $PWD $Target -} +# Capture trailing-separator intent before GetUnresolvedProviderPathFromPSPath normalizes it away; +# a trailing separator is an explicit signal that the target is a container, not a file. +$endsWithSeparator = $Target -and + $Target[-1] -in @([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) + +# Resolve PSDrive paths (e.g. TestDrive:) and relative paths to absolute filesystem paths +$Target = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Target) $TargetParent = Split-Path $Target -Parent $PathToAdd = $Target @@ -94,24 +97,31 @@ if (Test-Path $Target -PathType Leaf) { } $PathToAdd = Split-Path $Target -Parent } -elseif ([IO.Path]::GetExtension($Target) -and -not (Test-Path $Target -PathType Container)) { - # Target has a file extension — treat as a full destination file path +elseif ( + -not $endsWithSeparator -and + -not (Test-Path $Target -PathType Container) -and + ([IO.Path]::GetExtension($Target) -or (Test-Path $TargetParent)) +) { + # Treat as a full destination file path. + # Triggered when: has a file extension, OR parent already exists and caller gave no trailing separator. + # The trailing-separator check preserves extensionless binary targets (e.g. /usr/local/bin/terraform) + # while still allowing directory-like targets to be signalled with a trailing slash. + $PathToAdd = $TargetParent if (-not (Test-Path $TargetParent)) { Write-Error "Could not find parent path [$TargetParent] for target [$Target]" if ($PSDependAction -contains 'Test') { return $False } + return } - else { - $Path = $Target - $ToInstall = $True - Write-Verbose "Target has extension, treating as file path [$Target]" - } + $Path = $Target + $ToInstall = $True + Write-Verbose "Treating as destination file path [$Target]" } else { - # No extension (or already a container) — treat target as a directory + # No extension (or already a container, or explicit trailing separator) — treat target as a directory Write-Verbose "[$Target] is a container, creating path to file" - if (-not (Test-Path $Target)) { + if (-not (Test-Path $Target) -and $PSDependAction -contains 'Install') { New-Item -ItemType Directory -Path $Target -Force | Out-Null } If ($Name) { diff --git a/Tests/FileDownload.Type.Tests.ps1 b/Tests/FileDownload.Type.Tests.ps1 index 6b64d9b..3777a54 100644 --- a/Tests/FileDownload.Type.Tests.ps1 +++ b/Tests/FileDownload.Type.Tests.ps1 @@ -73,8 +73,10 @@ Describe 'FileDownload script' -Skip:$SkipUnsupported { } It 'Creates a new directory and downloads into it when Target has no extension and does not exist' { - $newDir = Join-Path (New-Item 'TestDrive:/dl5base' -ItemType Directory -Force).FullName 'newcontainer' - $dep = New-PSDependFixture -DependencyName 'https://example.com/sample.dll' -DependencyType 'FileDownload' -Target $newDir + $base = (New-Item 'TestDrive:/dl5base' -ItemType Directory -Force).FullName + $newDir = Join-Path $base 'newcontainer' + # Trailing separator signals "this is a container, not an extensionless file" + $dep = New-PSDependFixture -DependencyName 'https://example.com/sample.dll' -DependencyType 'FileDownload' -Target "$newDir/" InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath; T = $newDir } { & $ScriptPath -Dependency $Dep } @@ -100,7 +102,8 @@ Describe 'FileDownload script' -Skip:$SkipUnsupported { $baseDir = (New-Item 'TestDrive:/relbase' -ItemType Directory -Force).FullName Push-Location $baseDir try { - $dep = New-PSDependFixture -DependencyName 'https://example.com/sample.dll' -DependencyType 'FileDownload' -Target 'subdir' + # Trailing separator signals "this is a container, not an extensionless file" + $dep = New-PSDependFixture -DependencyName 'https://example.com/sample.dll' -DependencyType 'FileDownload' -Target 'subdir/' InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { & $ScriptPath -Dependency $Dep }