Skip to content

Commit 72730e0

Browse files
niheavenr15ch13
authored andcommitted
decompress.ps1: Refactor extraction handling functions (#3204)
* Refactored functions: * requires_7zip > Test-7ZipRequirement * requires_lessmsi > Test-LessMSIRequirement * extract_7zip > Expand-7ZipArchive * extract_msi/extract_lessmsi > Expand-MSIArchive * unpack_inno > Expand-InnoArchive * extract_zip/unzip_old > Expand-ZipArchive * Fix 7zip failed when .tar and .tgz have diff names * Add tools to CI * Add test cases for Expand-7ZipArchive * Add test cases for Expand-InnoArchive * Add test cases for Expand-MSIArchive * Add config `7ZIPEXTRACT_USE_EXTERNAL` * Change '$recurse' to '$removal'
1 parent 19a1ab3 commit 72730e0

File tree

8 files changed

+353
-185
lines changed

8 files changed

+353
-185
lines changed

lib/decompress.ps1

+183-98
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,208 @@
1-
function requires_7zip($manifest, $architecture) {
2-
foreach($dlurl in @(url $manifest $architecture)) {
3-
if(file_requires_7zip $dlurl) { return $true }
1+
function Test-7ZipRequirement {
2+
[CmdletBinding(DefaultParameterSetName = "URL")]
3+
[OutputType([Boolean])]
4+
param(
5+
[Parameter(Mandatory = $true, ParameterSetName = "URL")]
6+
[String[]]
7+
$URL,
8+
[Parameter(Mandatory = $true, ParameterSetName = "File")]
9+
[String]
10+
$File
11+
)
12+
if ($URL) {
13+
if (get_config 7ZIPEXTRACT_USE_EXTERNAL) {
14+
return $false
15+
} else {
16+
return ($URL | Where-Object { Test-7ZipRequirement -File $_ }).Count -gt 0
17+
}
18+
} else {
19+
return $File -match '\.((gz)|(tar)|(tgz)|(lzma)|(bz)|(bz2)|(7z)|(rar)|(iso)|(xz)|(lzh)|(nupkg))$'
420
}
521
}
622

7-
function requires_lessmsi ($manifest, $architecture) {
8-
$useLessMsi = get_config MSIEXTRACT_USE_LESSMSI
9-
if (!$useLessMsi) { return $false }
10-
11-
$(url $manifest $architecture | Where-Object {
12-
$_ -match '\.(msi)$'
13-
} | Measure-Object | Select-Object -exp count) -gt 0
14-
}
15-
16-
function file_requires_7zip($fname) {
17-
$fname -match '\.((gz)|(tar)|(tgz)|(lzma)|(bz)|(bz2)|(7z)|(rar)|(iso)|(xz)|(lzh)|(nupkg))$'
18-
}
19-
20-
function extract_7zip($path, $to, $recurse) {
21-
$output = 7z x "$path" -o"$to" -y
22-
if($lastexitcode -ne 0) { abort "Exit code was $lastexitcode." }
23-
24-
# check for tar
25-
$tar = (split-path $path -leaf) -replace '\.[^\.]*$', ''
26-
if($tar -match '\.tar$') {
27-
if(test-path "$to\$tar") { extract_7zip "$to\$tar" $to $true }
23+
function Test-LessMSIRequirement {
24+
[CmdletBinding()]
25+
[OutputType([Boolean])]
26+
param(
27+
[Parameter(Mandatory = $true)]
28+
[String[]]
29+
$URL
30+
)
31+
if (get_config MSIEXTRACT_USE_LESSMSI) {
32+
return ($URL | Where-Object { $_ -match '\.msi$' }).Count -gt 0
33+
} else {
34+
return $false
2835
}
29-
30-
if($recurse) { Remove-Item $path } # clean up intermediate files
31-
}
32-
33-
function extract_msi($path, $to) {
34-
$logfile = "$(split-path $path)\msi.log"
35-
$ok = run 'msiexec' @('/a', "`"$path`"", '/qn', "TARGETDIR=`"$to`"", "/lwe `"$logfile`"")
36-
if(!$ok) { abort "Failed to extract files from $path.`nLog file:`n $(friendly_path $logfile)" }
37-
if(test-path $logfile) { Remove-Item $logfile }
3836
}
3937

40-
function lessmsi_config ($extract_dir) {
41-
$extract_fn = 'extract_lessmsi'
42-
if ($extract_dir) {
43-
$extract_dir = join-path SourceDir $extract_dir
38+
function Expand-7ZipArchive {
39+
[CmdletBinding()]
40+
param(
41+
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
42+
[String]
43+
$Path,
44+
[Parameter(Position = 1)]
45+
[String]
46+
$DestinationPath = (Split-Path $Path),
47+
[Switch]
48+
$Removal
49+
)
50+
$LogLocation = "$(Split-Path $Path)\7zip.log"
51+
if (get_config 7ZIPEXTRACT_USE_EXTERNAL) {
52+
try {
53+
7z x "$Path" -o"$DestinationPath" -y | Out-File $LogLocation
54+
} catch [System.Management.Automation.CommandNotFoundException] {
55+
abort "Cannot find external 7Zip (7z.exe) while '7ZIPEXTRACT_USE_EXTERNAL' is 'true'!`nRun 'scoop config 7ZIPEXTRACT_USE_EXTERNAL false' or install 7Zip manually and try again."
56+
}
4457
} else {
45-
$extract_dir = "SourceDir"
58+
&(file_path 7zip 7z.exe) x "$Path" -o"$DestinationPath" -y | Out-File $LogLocation
59+
}
60+
if ($LASTEXITCODE -ne 0) {
61+
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogLocation)"
62+
}
63+
if (Test-Path $LogLocation) {
64+
Remove-Item $LogLocation -Force
65+
}
66+
if ((strip_ext $Path) -match '\.tar$' -or $Path -match '\.tgz$') {
67+
# Check for tar
68+
$ArchivedFile = &(file_path 7zip 7z.exe) l "$Path"
69+
if ($LASTEXITCODE -eq 0) {
70+
$TarFile = $ArchivedFile[-3] -replace '.{53}(.*)', '$1' # get inner tar file name
71+
Expand-7ZipArchive "$DestinationPath\$TarFile" $DestinationPath -Removal
72+
} else {
73+
abort "Failed to list files in $Path.`nNot a 7Zip supported archive file."
74+
}
75+
}
76+
if ($Removal) {
77+
# Remove original archive file
78+
Remove-Item $Path -Force
4679
}
47-
48-
$extract_fn, $extract_dir
4980
}
5081

51-
function extract_lessmsi($path, $to) {
52-
Invoke-Expression "lessmsi x `"$path`" `"$to\`""
82+
function Expand-MSIArchive {
83+
[CmdletBinding()]
84+
param(
85+
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
86+
[String]
87+
$Path,
88+
[Parameter(Position = 1)]
89+
[String]
90+
$DestinationPath = (Split-Path $Path),
91+
[Switch]
92+
$Removal
93+
)
94+
$LogLocation = "$(Split-Path $Path)\msi.log"
95+
if (get_config MSIEXTRACT_USE_LESSMSI) {
96+
&(file_path lessmsi lessmsi.exe) x "$Path" "$DestinationPath\" | Out-File $LogLocation
97+
if ($LASTEXITCODE -ne 0) {
98+
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogLocation)"
99+
}
100+
if (Test-Path "$DestinationPath\SourceDir") {
101+
movedir "$DestinationPath\SourceDir" "$DestinationPath" | Out-Null
102+
}
103+
} else {
104+
$ok = run 'msiexec' @('/a', "`"$Path`"", '/qn', "TARGETDIR=`"$DestinationPath`"", "/lwe `"$LogLocation`"")
105+
if (!$ok) {
106+
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogLocation)"
107+
}
108+
Remove-Item "$DestinationPath\$(fname $Path)" -Force
109+
}
110+
if (Test-Path $LogLocation) {
111+
Remove-Item $LogLocation -Force
112+
}
113+
if ($Removal) {
114+
# Remove original archive file
115+
Remove-Item $Path -Force
116+
}
53117
}
54118

55-
function unpack_inno($fname, $manifest, $dir) {
56-
if (!$manifest.innosetup) { return }
57-
58-
write-host "Unpacking innosetup... " -nonewline
59-
innounp -x -d"$dir\_scoop_unpack" "$dir\$fname" > "$dir\innounp.log"
60-
if ($lastexitcode -ne 0) {
61-
abort "Failed to unpack innosetup file. See $dir\innounp.log"
119+
function Expand-InnoArchive {
120+
[CmdletBinding()]
121+
param(
122+
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
123+
[String]
124+
$Path,
125+
[Parameter(Position = 1)]
126+
[String]
127+
$DestinationPath = (Split-Path $Path),
128+
[Switch]
129+
$Removal
130+
)
131+
$LogLocation = "$(Split-Path $Path)\innounp.log"
132+
&(file_path innounp innounp.exe) -x -d"$DestinationPath" -c'{app}' "$Path" -y | Out-File $LogLocation
133+
if ($LASTEXITCODE -ne 0) {
134+
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogLocation)"
135+
}
136+
if (Test-Path $LogLocation) {
137+
Remove-Item $LogLocation -Force
138+
}
139+
if ($Removal) {
140+
# Remove original archive file
141+
Remove-Item $Path -Force
62142
}
63-
64-
Get-ChildItem "$dir\_scoop_unpack\{app}" -r | Move-Item -dest "$dir" -force
65-
66-
Remove-Item -r -force "$dir\_scoop_unpack"
67-
68-
Remove-Item "$dir\$fname"
69-
Write-Host "done." -f Green
70143
}
71144

72-
function extract_zip($path, $to) {
73-
if (!(test-path $path)) { abort "can't find $path to unzip"}
74-
try { add-type -assembly "System.IO.Compression.FileSystem" -ea stop }
75-
catch { unzip_old $path $to; return } # for .net earlier than 4.5
76-
$retries = 0
77-
while ($retries -le 10) {
78-
if ($retries -eq 10) {
145+
function Expand-ZipArchive {
146+
[CmdletBinding()]
147+
param(
148+
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
149+
[String]
150+
$Path,
151+
[Parameter(Position = 1)]
152+
[String]
153+
$DestinationPath = (Split-Path $Path),
154+
[Switch]
155+
$Removal
156+
)
157+
# All methods to unzip the file require .NET4.5+
158+
if ($PSVersionTable.PSVersion.Major -lt 5) {
159+
Add-Type -AssemblyName System.IO.Compression.FileSystem
160+
try {
161+
[System.IO.Compression.ZipFile]::ExtractToDirectory($Path, $DestinationPath)
162+
} catch [System.IO.PathTooLongException] {
163+
# try to fall back to 7zip if path is too long
79164
if (7zip_installed) {
80-
extract_7zip $path $to $false
165+
Expand-7ZipArchive $Path $DestinationPath -Removal
81166
return
82167
} else {
83-
abort "Unzip failed: Windows can't unzip because a process is locking the file.`nRun 'scoop install 7zip' and try again."
168+
abort "Unzip failed: Windows can't handle the long paths in this zip file.`nRun 'scoop install 7zip' and try again."
84169
}
170+
} catch [System.IO.IOException] {
171+
if (7zip_installed) {
172+
Expand-7ZipArchive $Path $DestinationPath -Removal
173+
return
174+
} else {
175+
abort "Unzip failed: Windows can't handle the file names in this zip file.`nRun 'scoop install 7zip' and try again."
176+
}
177+
} catch {
178+
abort "Unzip failed: $_"
85179
}
86-
if (isFileLocked $path) {
87-
write-host "Waiting for $path to be unlocked by another process... ($retries/10)"
88-
$retries++
89-
Start-Sleep -s 2
90-
} else {
91-
break
92-
}
180+
} else {
181+
# Use Expand-Archive to unzip in PowerShell 5+
182+
Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force
93183
}
94-
95-
try {
96-
[io.compression.zipfile]::extracttodirectory($path, $to)
97-
} catch [system.io.pathtoolongexception] {
98-
# try to fall back to 7zip if path is too long
99-
if (7zip_installed) {
100-
extract_7zip $path $to $false
101-
return
102-
} else {
103-
abort "Unzip failed: Windows can't handle the long paths in this zip file.`nRun 'scoop install 7zip' and try again."
104-
}
105-
} catch [system.io.ioexception] {
106-
if (7zip_installed) {
107-
extract_7zip $path $to $false
108-
return
109-
} else {
110-
abort "Unzip failed: Windows can't handle the file names in this zip file.`nRun 'scoop install 7zip' and try again."
111-
}
112-
} catch {
113-
abort "Unzip failed: $_"
184+
if ($Removal) {
185+
# Remove original archive file
186+
Remove-Item $Path -Force
114187
}
115188
}
116189

117-
function unzip_old($path, $to) {
118-
# fallback for .net earlier than 4.5
119-
$shell = (new-object -com shell.application -strict)
120-
$zipfiles = $shell.namespace("$path").items()
121-
$to = ensure $to
122-
$shell.namespace("$to").copyHere($zipfiles, 4) # 4 = don't show progress dialog
190+
function extract_7zip($path, $to, $removal) {
191+
Show-DeprecationMessage $MyInvocation 'Expand-7ZipArchive'
192+
Expand-7ZipArchive -Path $path -DestinationPath $to -Removal:$removal
193+
}
194+
195+
function extract_msi($path, $to, $removal) {
196+
Show-DeprecationMessage $MyInvocation 'Expand-MSIArchive'
197+
Expand-MSIArchive -Path $path -DestinationPath $to -Removal:$removal
198+
}
199+
200+
function extract_inno($path, $to, $removal) {
201+
Show-DeprecationMessage $MyInvocation 'Expand-InnoArchive'
202+
Expand-InnoArchive -Path $path -DestinationPath $to -Removal:$removal
203+
}
204+
205+
function extract_zip($path, $to, $removal) {
206+
Show-DeprecationMessage $MyInvocation 'Expand-ZipArchive'
207+
Expand-ZipArchive -Path $path -DestinationPath $to -Removal:$removal
123208
}

lib/depends.ps1

+12-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# resolve dependencies for the supplied apps, and sort into the correct order
22
function install_order($apps, $arch) {
33
$res = @()
4-
foreach($app in $apps) {
5-
foreach($dep in deps $app $arch) {
6-
if($res -notcontains $dep) { $res += $dep}
4+
foreach ($app in $apps) {
5+
foreach ($dep in deps $app $arch) {
6+
if ($res -notcontains $dep) { $res += $dep}
77
}
8-
if($res -notcontains $app) { $res += $app }
8+
if ($res -notcontains $app) { $res += $app }
99
}
1010
return $res
1111
}
@@ -15,7 +15,7 @@ function deps($app, $arch) {
1515
$resolved = new-object collections.arraylist
1616
dep_resolve $app $arch $resolved @()
1717

18-
if($resolved.count -eq 1) { return @() } # no dependencies
18+
if ($resolved.count -eq 1) { return @() } # no dependencies
1919
return $resolved[0..($resolved.count - 2)]
2020
}
2121

@@ -33,9 +33,9 @@ function dep_resolve($app, $arch, $resolved, $unresolved) {
3333

3434
$deps = @(install_deps $manifest $arch) + @(runtime_deps $manifest) | Select-Object -Unique
3535

36-
foreach($dep in $deps) {
37-
if($resolved -notcontains $dep) {
38-
if($unresolved -contains $dep) {
36+
foreach ($dep in $deps) {
37+
if ($resolved -notcontains $dep) {
38+
if ($unresolved -contains $dep) {
3939
abort "Circular dependency detected: '$app' -> '$dep'."
4040
}
4141
dep_resolve $dep $arch $resolved $unresolved
@@ -46,17 +46,15 @@ function dep_resolve($app, $arch, $resolved, $unresolved) {
4646
}
4747

4848
function runtime_deps($manifest) {
49-
if($manifest.depends) { return $manifest.depends }
49+
if ($manifest.depends) { return $manifest.depends }
5050
}
5151

5252
function install_deps($manifest, $arch) {
5353
$deps = @()
5454

55-
if((requires_7zip $manifest $arch) -and !(7zip_installed)) {
56-
$deps += '7zip'
57-
}
58-
if(requires_lessmsi $manifest $arch) { $deps += 'lessmsi' }
59-
if($manifest.innosetup) { $deps += 'innounp' }
55+
if (Test-7ZipRequirement -URL (url $manifest $arch)) { $deps += '7zip' }
56+
if (Test-LessMSIRequirement -URL (url $manifest $arch)) { $deps += 'lessmsi' }
57+
if ($manifest.innosetup) { $deps += 'innounp' }
6058

6159
return $deps
6260
}

0 commit comments

Comments
 (0)