mirror of
https://github.com/ScoopInstaller/Scoop.git
synced 2026-10-02 18:18:24 +00:00
Due to my prior PR #2562 which changed the content of .ps1 shims, the code that attempts to warn about shims being overwritten no longer detects app name correctly. This is to correct that.
701 lines
23 KiB
PowerShell
701 lines
23 KiB
PowerShell
# Note: The default directory changed from ~/AppData/Local/scoop to ~/scoop
|
|
# on 1 Nov, 2016 to work around long paths used by NodeJS.
|
|
# Old installations should continue to work using the old path.
|
|
# There is currently no automatic migration path to deal
|
|
# with updating old installations to the new path.
|
|
$scoopdir = $env:SCOOP, "$env:USERPROFILE\scoop" | Select-Object -first 1
|
|
|
|
$oldscoopdir = "$env:LOCALAPPDATA\scoop"
|
|
if((test-path $oldscoopdir) -and !$env:SCOOP) {
|
|
$scoopdir = $oldscoopdir
|
|
}
|
|
|
|
$globaldir = $env:SCOOP_GLOBAL, "$env:ProgramData\scoop" | Select-Object -first 1
|
|
|
|
# Note: Setting the SCOOP_CACHE environment variable to use a shared directory
|
|
# is experimental and untested. There may be concurrency issues when
|
|
# multiple users write and access cached files at the same time.
|
|
# Use at your own risk.
|
|
$cachedir = $env:SCOOP_CACHE, "$scoopdir\cache" | Select-Object -first 1
|
|
|
|
# Note: Github disabled TLS 1.0 support on 2018-02-23. Need to enable TLS 1.2
|
|
# for all communication with api.github.com
|
|
function enable-encryptionscheme([Net.SecurityProtocolType]$scheme) {
|
|
# Net.SecurityProtocolType is a [Flags] enum, binary-OR sets
|
|
# the specified scheme in addition to whatever scheme is already active
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor $scheme
|
|
}
|
|
enable-encryptionscheme "Tls12"
|
|
|
|
function Get-UserAgent() {
|
|
return "Scoop/1.0 (+http://scoop.sh/) PowerShell/$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor) (Windows NT $([System.Environment]::OSVersion.Version.Major).$([System.Environment]::OSVersion.Version.Minor); $(if($env:PROCESSOR_ARCHITECTURE -eq 'AMD64'){'Win64; x64; '})$(if($env:PROCESSOR_ARCHITEW6432 -eq 'AMD64'){'WOW64; '})$PSEdition)"
|
|
}
|
|
|
|
# helper functions
|
|
function coalesce($a, $b) { if($a) { return $a } $b }
|
|
|
|
function format($str, $hash) {
|
|
$hash.keys | ForEach-Object { set-variable $_ $hash[$_] }
|
|
$executionContext.invokeCommand.expandString($str)
|
|
}
|
|
function is_admin {
|
|
$admin = [security.principal.windowsbuiltinrole]::administrator
|
|
$id = [security.principal.windowsidentity]::getcurrent()
|
|
([security.principal.windowsprincipal]($id)).isinrole($admin)
|
|
}
|
|
|
|
# messages
|
|
function abort($msg, [int] $exit_code=1) { write-host $msg -f red; exit $exit_code }
|
|
function error($msg) { write-host "ERROR $msg" -f darkred }
|
|
function warn($msg) { write-host "WARN $msg" -f darkyellow }
|
|
function info($msg) { write-host "INFO $msg" -f darkgray }
|
|
function debug($msg, $indent = $false) {
|
|
if($indent) {
|
|
write-host " DEBUG $msg" -f darkcyan
|
|
} else {
|
|
write-host "DEBUG $msg" -f darkcyan
|
|
}
|
|
}
|
|
function success($msg) { write-host $msg -f darkgreen }
|
|
|
|
function filesize($length) {
|
|
$gb = [math]::pow(2, 30)
|
|
$mb = [math]::pow(2, 20)
|
|
$kb = [math]::pow(2, 10)
|
|
|
|
if($length -gt $gb) {
|
|
"{0:n1} GB" -f ($length / $gb)
|
|
} elseif($length -gt $mb) {
|
|
"{0:n1} MB" -f ($length / $mb)
|
|
} elseif($length -gt $kb) {
|
|
"{0:n1} KB" -f ($length / $kb)
|
|
} else {
|
|
"$($length) B"
|
|
}
|
|
}
|
|
|
|
# dirs
|
|
function basedir($global) { if($global) { return $globaldir } $scoopdir }
|
|
function appsdir($global) { "$(basedir $global)\apps" }
|
|
function shimdir($global) { "$(basedir $global)\shims" }
|
|
function appdir($app, $global) { "$(appsdir $global)\$app" }
|
|
function versiondir($app, $version, $global) { "$(appdir $app $global)\$version" }
|
|
function persistdir($app, $global) { "$(basedir $global)\persist\$app" }
|
|
function usermanifestsdir { "$(basedir)\workspace" }
|
|
function usermanifest($app) { "$(usermanifestsdir)\$app.json" }
|
|
function cache_path($app, $version, $url) { "$cachedir\$app#$version#$($url -replace '[^\w\.\-]+', '_')" }
|
|
|
|
# apps
|
|
function sanitary_path($path) { return [regex]::replace($path, "[/\\?:*<>|]", "") }
|
|
function installed($app, $global=$null) {
|
|
if($null -eq $global) { return (installed $app $true) -or (installed $app $false) }
|
|
# Dependencies of the format "bucket/dependency" install in a directory of form
|
|
# "dependency". So we need to extract the bucket from the name and only give the app
|
|
# name to is_directory
|
|
$app = $app.split("/")[-1]
|
|
return is_directory (appdir $app $global)
|
|
}
|
|
function installed_apps($global) {
|
|
$dir = appsdir $global
|
|
if(test-path $dir) {
|
|
Get-ChildItem $dir | Where-Object { $_.psiscontainer -and $_.name -ne 'scoop' } | ForEach-Object { $_.name }
|
|
}
|
|
}
|
|
|
|
function file_path($app, $file) {
|
|
# normal path to file
|
|
$path = "$(versiondir $app 'current' $false)\$file"
|
|
if(Test-Path($path)) {
|
|
return $path
|
|
}
|
|
|
|
# global path to file
|
|
$path = "$(versiondir $app 'current' $true)\$file"
|
|
if(Test-Path($path)) {
|
|
return $path
|
|
}
|
|
|
|
# not found
|
|
return $null
|
|
}
|
|
|
|
function 7zip_path() {
|
|
return (file_path '7zip' '7z.exe')
|
|
}
|
|
|
|
function 7zip_installed() {
|
|
return ![String]::IsNullOrWhiteSpace("$(7zip_path)")
|
|
}
|
|
|
|
function aria2_path() {
|
|
return (file_path 'aria2' 'aria2c.exe')
|
|
}
|
|
|
|
function aria2_installed() {
|
|
return ![String]::IsNullOrWhiteSpace("$(aria2_path)")
|
|
}
|
|
|
|
function aria2_enabled() {
|
|
return (aria2_installed) -and (get_config 'aria2-enabled' $true)
|
|
}
|
|
|
|
function app_status($app, $global) {
|
|
$status = @{}
|
|
$status.installed = (installed $app $global)
|
|
$status.version = current_version $app $global
|
|
$status.latest_version = $status.version
|
|
|
|
$install_info = install_info $app $status.version $global
|
|
|
|
$status.failed = (!$install_info -or !$status.version)
|
|
|
|
$manifest = manifest $app $install_info.bucket $install_info.url
|
|
$status.removed = (!$manifest)
|
|
if($manifest.version) {
|
|
$status.latest_version = $manifest.version
|
|
}
|
|
|
|
$status.outdated = $false
|
|
if($status.version -and $status.latest_version) {
|
|
$status.outdated = ((compare_versions $status.latest_version $status.version) -gt 0)
|
|
}
|
|
|
|
$status.missing_deps = @()
|
|
$deps = @(runtime_deps $manifest) | Where-Object {
|
|
$app, $bucket, $null = parse_app $_
|
|
return !(installed $app)
|
|
}
|
|
if($deps) {
|
|
$status.missing_deps += ,$deps
|
|
}
|
|
|
|
return $status
|
|
}
|
|
|
|
function appname_from_url($url) {
|
|
(split-path $url -leaf) -replace '.json$', ''
|
|
}
|
|
|
|
# paths
|
|
function fname($path) { split-path $path -leaf }
|
|
function strip_ext($fname) { $fname -replace '\.[^\.]*$', '' }
|
|
function strip_filename($path) { $path -replace [regex]::escape((fname $path)) }
|
|
function strip_fragment($url) { $url -replace (new-object uri $url).fragment }
|
|
|
|
function url_filename($url) {
|
|
(split-path $url -leaf).split('?') | Select-Object -First 1
|
|
}
|
|
# Unlike url_filename which can be tricked by appending a
|
|
# URL fragment (e.g. #/dl.7z, useful for coercing a local filename),
|
|
# this function extracts the original filename from the URL.
|
|
function url_remote_filename($url) {
|
|
split-path (new-object uri $url).absolutePath -leaf
|
|
}
|
|
|
|
function ensure($dir) { if(!(test-path $dir)) { mkdir $dir > $null }; resolve-path $dir }
|
|
function fullpath($path) { # should be ~ rooted
|
|
$executionContext.sessionState.path.getUnresolvedProviderPathFromPSPath($path)
|
|
}
|
|
function relpath($path) { "$($myinvocation.psscriptroot)\$path" } # relative to calling script
|
|
function friendly_path($path) {
|
|
$h = (Get-PsProvider 'FileSystem').home; if(!$h.endswith('\')) { $h += '\' }
|
|
if($h -eq '\') { return $path }
|
|
return "$path" -replace ([regex]::escape($h)), "~\"
|
|
}
|
|
function is_local($path) {
|
|
($path -notmatch '^https?://') -and (test-path $path)
|
|
}
|
|
|
|
# operations
|
|
function dl($url,$to) {
|
|
$wc = New-Object Net.Webclient
|
|
$wc.headers.add('Referer', (strip_filename $url))
|
|
$wc.Headers.Add('User-Agent', (Get-UserAgent))
|
|
$wc.downloadFile($url,$to)
|
|
}
|
|
|
|
function env($name,$global,$val='__get') {
|
|
$target = 'User'; if($global) {$target = 'Machine'}
|
|
if($val -eq '__get') { [environment]::getEnvironmentVariable($name,$target) }
|
|
else { [environment]::setEnvironmentVariable($name,$val,$target) }
|
|
}
|
|
|
|
function isFileLocked([string]$path) {
|
|
$file = New-Object System.IO.FileInfo $path
|
|
|
|
if ((Test-Path -Path $path) -eq $false) {
|
|
return $false
|
|
}
|
|
|
|
try {
|
|
$stream = $file.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
|
|
if ($stream) {
|
|
$stream.Close()
|
|
}
|
|
return $false
|
|
}
|
|
catch {
|
|
# file is locked by a process.
|
|
return $true
|
|
}
|
|
}
|
|
|
|
function unzip($path, $to) {
|
|
if (!(test-path $path)) { abort "can't find $path to unzip"}
|
|
try { add-type -assembly "System.IO.Compression.FileSystem" -ea stop }
|
|
catch { unzip_old $path $to; return } # for .net earlier than 4.5
|
|
$retries = 0
|
|
while ($retries -le 10) {
|
|
if ($retries -eq 10) {
|
|
if (7zip_installed) {
|
|
extract_7zip $path $to $false
|
|
return
|
|
} else {
|
|
abort "Unzip failed: Windows can't unzip because a process is locking the file.`nRun 'scoop install 7zip' and try again."
|
|
}
|
|
}
|
|
if (isFileLocked $path) {
|
|
write-host "Waiting for $path to be unlocked by another process... ($retries/10)"
|
|
$retries++
|
|
Start-Sleep -s 2
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
try {
|
|
[io.compression.zipfile]::extracttodirectory($path,$to)
|
|
} catch [system.io.pathtoolongexception] {
|
|
# try to fall back to 7zip if path is too long
|
|
if(7zip_installed) {
|
|
extract_7zip $path $to $false
|
|
return
|
|
} else {
|
|
abort "Unzip failed: Windows can't handle the long paths in this zip file.`nRun 'scoop install 7zip' and try again."
|
|
}
|
|
} catch [system.io.ioexception] {
|
|
if (7zip_installed) {
|
|
extract_7zip $path $to $false
|
|
return
|
|
} else {
|
|
abort "Unzip failed: Windows can't handle the file names in this zip file.`nRun 'scoop install 7zip' and try again."
|
|
}
|
|
} catch {
|
|
abort "Unzip failed: $_"
|
|
}
|
|
}
|
|
|
|
function unzip_old($path,$to) {
|
|
# fallback for .net earlier than 4.5
|
|
$shell = (new-object -com shell.application -strict)
|
|
$zipfiles = $shell.namespace("$path").items()
|
|
$to = ensure $to
|
|
$shell.namespace("$to").copyHere($zipfiles, 4) # 4 = don't show progress dialog
|
|
}
|
|
|
|
function is_directory([String] $path) {
|
|
return (Test-Path $path) -and (Get-Item $path) -is [System.IO.DirectoryInfo]
|
|
}
|
|
|
|
function movedir($from, $to) {
|
|
$from = $from.trimend('\')
|
|
$to = $to.trimend('\')
|
|
|
|
$proc = New-Object System.Diagnostics.Process
|
|
$proc.StartInfo.FileName = 'robocopy.exe'
|
|
$proc.StartInfo.Arguments = "`"$from`" `"$to`" /e /move"
|
|
$proc.StartInfo.RedirectStandardOutput = $true
|
|
$proc.StartInfo.RedirectStandardError = $true
|
|
$proc.StartInfo.UseShellExecute = $false
|
|
$proc.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
|
|
$proc.Start()
|
|
$out = $proc.StandardOutput.ReadToEnd()
|
|
$proc.WaitForExit()
|
|
|
|
if($proc.ExitCode -ge 8) {
|
|
debug $out
|
|
throw "Could not find '$(fname $from)'! (error $($proc.ExitCode))"
|
|
}
|
|
|
|
# wait for robocopy to terminate its threads
|
|
1..10 | ForEach-Object {
|
|
if (Test-Path $from) {
|
|
Start-Sleep -Milliseconds 100
|
|
}
|
|
}
|
|
}
|
|
|
|
function get_app_name($path) {
|
|
if ($path -match '([^/\\]+)[/\\]current[/\\]') {
|
|
return $matches[1].tolower()
|
|
}
|
|
return ''
|
|
}
|
|
|
|
function get_app_name_from_ps1_shim($shim_ps1) {
|
|
if (!(Test-Path($shim_ps1))) {
|
|
return ''
|
|
}
|
|
$content = (Get-Content $shim_ps1 -Encoding utf8) -join ' '
|
|
return get_app_name $content
|
|
}
|
|
|
|
function warn_on_overwrite($shim_ps1, $path) {
|
|
if (!(Test-Path($shim_ps1))) {
|
|
return
|
|
}
|
|
$shim_app = get_app_name_from_ps1_shim $shim_ps1
|
|
$path_app = get_app_name $path
|
|
if ($shim_app -eq $path_app) {
|
|
return
|
|
}
|
|
$filename = [System.IO.Path]::GetFileName($path)
|
|
warn "Overwriting shim to $filename installed from $shim_app"
|
|
}
|
|
|
|
function shim($path, $global, $name, $arg) {
|
|
if(!(test-path $path)) { abort "Can't shim '$(fname $path)': couldn't find '$path'." }
|
|
$abs_shimdir = ensure (shimdir $global)
|
|
if(!$name) { $name = strip_ext (fname $path) }
|
|
|
|
$shim = "$abs_shimdir\$($name.tolower())"
|
|
|
|
warn_on_overwrite "$shim.ps1" $path
|
|
|
|
# convert to relative path
|
|
Push-Location $abs_shimdir
|
|
$relative_path = resolve-path -relative $path
|
|
Pop-Location
|
|
$resolved_path = resolve-path $path
|
|
|
|
# if $path points to another drive resolve-path prepends .\ which could break shims
|
|
if($relative_path -match "^(.\\[\w]:).*$") {
|
|
write-output "`$path = `"$path`"" | out-file "$shim.ps1" -encoding utf8
|
|
} else {
|
|
# Setting PSScriptRoot in Shim if it is not defined, so the shim doesn't break in PowerShell 2.0
|
|
Write-Output "if (!(Test-Path Variable:PSScriptRoot)) { `$PSScriptRoot = Split-Path `$MyInvocation.MyCommand.Path -Parent }" | Out-File "$shim.ps1" -Encoding utf8
|
|
write-output "`$path = join-path `"`$psscriptroot`" `"$relative_path`"" | out-file "$shim.ps1" -Encoding utf8 -Append
|
|
}
|
|
|
|
if($path -match '\.jar$') {
|
|
"if(`$myinvocation.expectingInput) { `$input | & java -jar `$path $arg @args } else { & java -jar `$path $arg @args }" | out-file "$shim.ps1" -encoding utf8 -append
|
|
} else {
|
|
"if(`$myinvocation.expectingInput) { `$input | & `$path $arg @args } else { & `$path $arg @args }" | out-file "$shim.ps1" -encoding utf8 -append
|
|
}
|
|
|
|
if($path -match '\.exe$') {
|
|
# for programs with no awareness of any shell
|
|
Copy-Item "$(versiondir 'scoop' 'current')\supporting\shimexe\bin\shim.exe" "$shim.exe" -force
|
|
write-output "path = $resolved_path" | out-file "$shim.shim" -encoding utf8
|
|
if($arg) {
|
|
write-output "args = $arg" | out-file "$shim.shim" -encoding utf8 -append
|
|
}
|
|
} elseif($path -match '\.((bat)|(cmd))$') {
|
|
# shim .bat, .cmd so they can be used by programs with no awareness of PSH
|
|
"@`"$resolved_path`" $arg %*" | out-file "$shim.cmd" -encoding ascii
|
|
|
|
"#!/bin/sh`ncmd //C `"$resolved_path`" $arg `"$@`"" | out-file $shim -encoding ascii
|
|
} elseif($path -match '\.ps1$') {
|
|
# make ps1 accessible from cmd.exe
|
|
"@echo off
|
|
setlocal enabledelayedexpansion
|
|
set args=%*
|
|
:: replace problem characters in arguments
|
|
set args=%args:`"='%
|
|
set args=%args:(=``(%
|
|
set args=%args:)=``)%
|
|
set invalid=`"='
|
|
if !args! == !invalid! ( set args= )
|
|
powershell -noprofile -ex unrestricted `"& '$resolved_path' $arg %args%;exit `$lastexitcode`"" | out-file "$shim.cmd" -encoding ascii
|
|
|
|
"#!/bin/sh`npowershell.exe -ex unrestricted `"$resolved_path`" $arg `"$@`"" | out-file $shim -encoding ascii
|
|
} elseif($path -match '\.jar$') {
|
|
"@java -jar `"$resolved_path`" $arg %*" | out-file "$shim.cmd" -encoding ascii
|
|
"#!/bin/sh`njava -jar `"$resolved_path`" $arg `"$@`"" | out-file $shim -encoding ascii
|
|
}
|
|
}
|
|
|
|
function search_in_path($target) {
|
|
$path = (env 'PATH' $false) + ";" + (env 'PATH' $true)
|
|
foreach($dir in $path.split(';')) {
|
|
if(test-path "$dir\$target" -pathType leaf) {
|
|
return "$dir\$target"
|
|
}
|
|
}
|
|
}
|
|
|
|
function ensure_in_path($dir, $global) {
|
|
$path = env 'PATH' $global
|
|
$dir = fullpath $dir
|
|
if($path -notmatch [regex]::escape($dir)) {
|
|
write-output "Adding $(friendly_path $dir) to $(if($global){'global'}else{'your'}) path."
|
|
|
|
env 'PATH' $global "$dir;$path" # for future sessions...
|
|
$env:PATH = "$dir;$env:PATH" # for this session
|
|
}
|
|
}
|
|
|
|
function ensure_architecture($architecture_opt) {
|
|
if(!$architecture_opt) {
|
|
return default_architecture
|
|
}
|
|
$architecture_opt = $architecture_opt.ToString().ToLower()
|
|
switch($architecture_opt) {
|
|
{ @('64bit', '64', 'x64', 'amd64', 'x86_64', 'x86-64') -contains $_ } { return '64bit' }
|
|
{ @('32bit', '32', 'x86', 'i386', '386', 'i686') -contains $_ } { return '32bit' }
|
|
default { throw [System.ArgumentException] "Invalid architecture: '$architecture_opt'"}
|
|
}
|
|
}
|
|
|
|
function ensure_all_installed($apps, $global) {
|
|
$installed = @()
|
|
$apps | Select-Object -Unique | Where-Object { $_.name -ne 'scoop' } | ForEach-Object {
|
|
$app, $null, $null = parse_app $_
|
|
if(installed $app $false) {
|
|
$installed += ,@($app, $false)
|
|
} elseif (installed $app $true) {
|
|
if($global) {
|
|
$installed += ,@($app, $true)
|
|
} else {
|
|
error "'$app' isn't installed for your account, but it is installed globally."
|
|
warn "Try again with the --global (or -g) flag instead."
|
|
}
|
|
} else {
|
|
error "'$app' isn't installed."
|
|
}
|
|
}
|
|
return ,$installed
|
|
}
|
|
|
|
function strip_path($orig_path, $dir) {
|
|
if($null -eq $orig_path) { $orig_path = '' }
|
|
$stripped = [string]::join(';', @( $orig_path.split(';') | Where-Object { $_ -and $_ -ne $dir } ))
|
|
return ($stripped -ne $orig_path), $stripped
|
|
}
|
|
|
|
function remove_from_path($dir,$global) {
|
|
$dir = fullpath $dir
|
|
|
|
# future sessions
|
|
$was_in_path, $newpath = strip_path (env 'path' $global) $dir
|
|
if($was_in_path) {
|
|
write-output "Removing $(friendly_path $dir) from your path."
|
|
env 'path' $global $newpath
|
|
}
|
|
|
|
# current session
|
|
$was_in_path, $newpath = strip_path $env:PATH $dir
|
|
if($was_in_path) { $env:PATH = $newpath }
|
|
}
|
|
|
|
function ensure_scoop_in_path($global) {
|
|
$abs_shimdir = ensure (shimdir $global)
|
|
# be aggressive (b-e-aggressive) and install scoop first in the path
|
|
ensure_in_path $abs_shimdir $global
|
|
}
|
|
|
|
function ensure_robocopy_in_path {
|
|
if(!(Get-Command robocopy -ea ignore)) {
|
|
shim "C:\Windows\System32\Robocopy.exe" $false
|
|
}
|
|
}
|
|
|
|
function wraptext($text, $width) {
|
|
if(!$width) { $width = $host.ui.rawui.buffersize.width };
|
|
$width -= 1 # be conservative: doesn't seem to print the last char
|
|
|
|
$text -split '\r?\n' | ForEach-Object {
|
|
$line = ''
|
|
$_ -split ' ' | ForEach-Object {
|
|
if($line.length -eq 0) { $line = $_ }
|
|
elseif($line.length + $_.length + 1 -le $width) { $line += " $_" }
|
|
else { $lines += ,$line; $line = $_ }
|
|
}
|
|
$lines += ,$line
|
|
}
|
|
|
|
$lines -join "`n"
|
|
}
|
|
|
|
function pluralize($count, $singular, $plural) {
|
|
if($count -eq 1) { $singular } else { $plural }
|
|
}
|
|
|
|
# for dealing with user aliases
|
|
$default_aliases = @{
|
|
'cp' = 'copy-item'
|
|
'echo' = 'write-output'
|
|
'gc' = 'get-content'
|
|
'gci' = 'get-childitem'
|
|
'gcm' = 'get-command'
|
|
'gm' = 'get-member'
|
|
'iex' = 'invoke-expression'
|
|
'ls' = 'get-childitem'
|
|
'mkdir' = { new-item -type directory @args }
|
|
'mv' = 'move-item'
|
|
'rm' = 'remove-item'
|
|
'sc' = 'set-content'
|
|
'select' = 'select-object'
|
|
'sls' = 'select-string'
|
|
}
|
|
|
|
function reset_alias($name, $value) {
|
|
if($existing = get-alias $name -ea ignore | Where-Object { $_.options -match 'readonly' }) {
|
|
if($existing.definition -ne $value) {
|
|
write-host "Alias $name is read-only; can't reset it." -f darkyellow
|
|
}
|
|
return # already set
|
|
}
|
|
if($value -is [scriptblock]) {
|
|
if(!(test-path -path "function:script:$name")) {
|
|
new-item -path function: -name "script:$name" -value $value | out-null
|
|
}
|
|
return
|
|
}
|
|
|
|
set-alias $name $value -scope script -option allscope
|
|
}
|
|
|
|
function reset_aliases() {
|
|
# for aliases where there's a local function, re-alias so the function takes precedence
|
|
$aliases = get-alias | Where-Object { $_.options -notmatch 'readonly|allscope' } | ForEach-Object { $_.name }
|
|
get-childitem function: | ForEach-Object {
|
|
$fn = $_.name
|
|
if($aliases -contains $fn) {
|
|
set-alias $fn local:$fn -scope script
|
|
}
|
|
}
|
|
|
|
# set default aliases
|
|
$default_aliases.keys | ForEach-Object { reset_alias $_ $default_aliases[$_] }
|
|
}
|
|
|
|
# convert list of apps to list of ($app, $global) tuples
|
|
function applist($apps, $global) {
|
|
if(!$apps) { return @() }
|
|
return ,@($apps | ForEach-Object { ,@($_, $global) })
|
|
}
|
|
|
|
function parse_app([string] $app) {
|
|
if($app -match '(?:(?<bucket>[a-zA-Z0-9-]+)\/)?(?<app>.*.json$|[a-zA-Z0-9-.]+)(?:@(?<version>.*))?') {
|
|
return $matches['app'], $matches['bucket'], $matches['version']
|
|
}
|
|
return $app, $null, $null
|
|
}
|
|
|
|
function show_app($app, $bucket, $version) {
|
|
if($bucket) {
|
|
$app = "$bucket/$app"
|
|
}
|
|
if($version) {
|
|
$app = "$app@$version"
|
|
}
|
|
return $app
|
|
}
|
|
|
|
function last_scoop_update() {
|
|
$last_update = (scoop config lastupdate)
|
|
if(!$last_update) {
|
|
$last_update = [System.DateTime]::Now
|
|
}
|
|
|
|
if($last_update.GetType() -eq [System.String]) {
|
|
try {
|
|
$last_update = [System.DateTime]::Parse($last_update)
|
|
} catch {
|
|
$last_update = [System.DateTime]::Now
|
|
}
|
|
}
|
|
return $last_update
|
|
}
|
|
|
|
function is_scoop_outdated() {
|
|
$last_update = $(last_scoop_update)
|
|
$now = [System.DateTime]::Now
|
|
if($last_update -eq $now) {
|
|
scoop config lastupdate $now.ToString('o')
|
|
# enforce an update for the first time
|
|
return $true
|
|
}
|
|
return $last_update.AddHours(3) -lt [System.DateTime]::Now.ToLocalTime()
|
|
}
|
|
|
|
function substitute($entity, [Hashtable] $params) {
|
|
if ($entity -is [Array]) {
|
|
return $entity | ForEach-Object { substitute $_ $params }
|
|
} elseif ($entity -is [String]) {
|
|
$params.GetEnumerator() | ForEach-Object {
|
|
$entity = $entity.Replace($_.Name, $_.Value)
|
|
}
|
|
return $entity
|
|
}
|
|
}
|
|
|
|
function format_hash([String] $hash) {
|
|
$hash = $hash.toLower()
|
|
switch ($hash.Length)
|
|
{
|
|
32 { $hash = "md5:$hash" } # md5
|
|
40 { $hash = "sha1:$hash" } # sha1
|
|
64 { $hash = $hash } # sha256
|
|
128 { $hash = "sha512:$hash" } # sha512
|
|
default { $hash = $null }
|
|
}
|
|
return $hash
|
|
}
|
|
|
|
function format_hash_aria2([String] $hash) {
|
|
$hash = $hash -split ':' | Select-Object -Last 1
|
|
switch ($hash.Length)
|
|
{
|
|
32 { $hash = "md5=$hash" } # md5
|
|
40 { $hash = "sha-1=$hash" } # sha1
|
|
64 { $hash = "sha-256=$hash" } # sha256
|
|
128 { $hash = "sha-512=$hash" } # sha512
|
|
default { $hash = $null }
|
|
}
|
|
return $hash
|
|
}
|
|
|
|
function handle_special_urls($url)
|
|
{
|
|
# FossHub.com
|
|
if($url -match "^(.*fosshub.com\/)(?<name>.*)\/(?<filename>.*)$") {
|
|
# create an url to request to request the expiring url
|
|
$name = $matches['name'] -replace '.html',''
|
|
$filename = $matches['filename']
|
|
# the key is a random 24 chars long hex string, so lets use ' SCOOPSCOOP ' :)
|
|
$url = "https://www.fosshub.com/gensLink/$name/$filename/2053434f4f5053434f4f5020"
|
|
$url = (Invoke-WebRequest -Uri $url | Select-Object -ExpandProperty Content)
|
|
}
|
|
|
|
# Sourceforge.net
|
|
if ($url -match "(?:downloads\.)?sourceforge.net\/projects?\/(?<project>[^\/]+)\/(?:files\/)?(?<file>.*?)(?:$|\/download|\?)") {
|
|
# Reshapes the URL to avoid redirections
|
|
$url = "https://downloads.sourceforge.net/project/$($matches['project'])/$($matches['file'])"
|
|
}
|
|
return $url
|
|
}
|
|
|
|
function get_magic_bytes($file) {
|
|
if(!(Test-Path $file)) {
|
|
return ''
|
|
}
|
|
|
|
if((Get-Command Get-Content).parameters.ContainsKey('AsByteStream')) {
|
|
# PowerShell Core (6.0+) '-Encoding byte' is replaced by '-AsByteStream'
|
|
return Get-Content $file -AsByteStream -TotalCount 8
|
|
}
|
|
else {
|
|
return Get-Content $file -Encoding byte -TotalCount 8
|
|
}
|
|
}
|
|
|
|
function get_magic_bytes_pretty($file, $glue = ' ') {
|
|
if(!(Test-Path $file)) {
|
|
return ''
|
|
}
|
|
|
|
return (get_magic_bytes $file | ForEach-Object { $_.ToString('x2') }) -join $glue
|
|
}
|