提交 d9fbfc2d 编写于 作者: G glimmer

first commit

上级
# retain windows line-endings in case checked out on mac or linux
* text eol=crlf
*.exe -text
*.zip -text
*.dll -text
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
# The PowerShell Script Analyzer will generate a warning
# diagnostic record for this file due to a bug -
# https://github.com/PowerShell/PSScriptAnalyzer/issues/472
@{
# Only diagnostic records of the specified severity will be generated.
# Uncomment the following line if you only want Errors and Warnings but
# not Information diagnostic records.
Severity = @('Error','Warning')
# Analyze **only** the following rules. Use IncludeRules when you want
# to invoke only a small subset of the defualt rules.
# IncludeRules = @('PSAvoidDefaultValueSwitchParameter',
# 'PSMisleadingBacktick',
# 'PSMissingModuleManifestField',
# 'PSReservedCmdletChar',
# 'PSReservedParams',
# 'PSShouldProcess',
# 'PSUseApprovedVerbs',
# 'PSAvoidUsingCmdletAliases',
# 'PSUseDeclaredVarsMoreThanAssignments')
# Do not analyze the following rules. Use ExcludeRules when you have
# commented out the IncludeRules settings above and want to include all
# the default rules except for those you exclude below.
# Note: if a rule is in both IncludeRules and ExcludeRules, the rule
# will be excluded.
ExcludeRules = @(
# Currently Scoop widely uses Write-Host to output colored text.
'PSAvoidUsingWriteHost',
# Temporarily allow uses of Invoke-Expression,
# this command is used by some core functions and hard to be removed.
'PSAvoidUsingInvokeExpression',
# PSUseDeclaredVarsMoreThanAssignments doesn't currently work due to:
# https://github.com/PowerShell/PSScriptAnalyzer/issues/636
'PSUseDeclaredVarsMoreThanAssignments'
)
}
<p align="center">
<!--<img src="scoop.png" alt="Long live Scoop!"/>-->
<h1 align="center">Scoop</h1>
</p>
<p align="center">
<b><a href="https://github.com/ScoopInstaller/Scoop#what-does-scoop-do">Features</a></b>
|
<b><a href="https://github.com/ScoopInstaller/Scoop#installation">Installation</a></b>
|
<b><a href="https://github.com/ScoopInstaller/Scoop/wiki">Documentation</a></b>
</p>
- - -
<p align="center" >
<a href="https://github.com/ScoopInstaller/Scoop">
<img src="https://img.shields.io/github/languages/code-size/ScoopInstaller/Scoop.svg" alt="Code Size" />
</a>
<a href="https://github.com/ScoopInstaller/Scoop">
<img src="https://img.shields.io/github/repo-size/ScoopInstaller/Scoop.svg" alt="Repository size" />
</a>
<a href="https://ci.appveyor.com/project/ScoopInstaller/Scoop">
<img src="https://ci.appveyor.com/api/projects/status/05foxatmrqo0l788?svg=true" alt="Build Status" />
</a>
<a href="https://discord.gg/s9yRQHt">
<img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
</a>
<a href="https://gitter.im/ScoopInstaller/Scoop">
<img src="https://badges.gitter.im/ScoopInstaller/Scoop.png" alt="Gitter Chat" />
</a>
<a href="https://github.com/ScoopInstaller/Scoop/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/ScoopInstaller/Scoop.svg" alt="License" />
</a>
</p>
Scoop is a command-line installer for Windows.
## What does Scoop do?
Scoop installs programs from the command line with a minimal amount of friction. It tries to eliminate things like:
- Permission popup windows
- GUI wizard-style installers
- Path pollution from installing lots of programs
- Unexpected side-effects from installing and uninstalling programs
- The need to find and install dependencies
- The need to perform extra setup steps to get a working program
Scoop is very scriptable, so you can run repeatable setups to get your environment just the way you like, e.g.:
```powershell
scoop install sudo
sudo scoop install 7zip git openssh --global
scoop install aria2 curl grep sed less touch
scoop install python ruby go perl
```
If you've built software that you'd like others to use, Scoop is an alternative to building an installer (e.g. MSI or InnoSetup) — you just need to zip your program and provide a JSON manifest that describes how to install it.
## Requirements
- Windows 7 SP1+ / Windows Server 2008+
- [PowerShell 5](https://aka.ms/wmf5download) (or later, include [PowerShell Core](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows?view=powershell-6)) and [.NET Framework 4.5](https://www.microsoft.com/net/download) (or later)
- PowerShell must be enabled for your user account e.g. `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`
## Installation
Run the following command from your PowerShell to install scoop to its default location (`C:\Users\<user>\scoop`)
```powershell
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
# or shorter
iwr -useb get.scoop.sh | iex
```
Once installed, run `scoop help` for instructions.
The default setup is configured so all user installed programs and Scoop itself live in `C:\Users\<user>\scoop`.
Globally installed programs (`--global`) live in `C:\ProgramData\scoop`.
These settings can be changed through environment variables.
### Install Scoop to a Custom Directory by changing `SCOOP`
```powershell
$env:SCOOP='D:\Applications\Scoop'
[Environment]::SetEnvironmentVariable('SCOOP', $env:SCOOP, 'User')
# run the installer
```
### Configure Scoop to install global programs to a Custom Directory by changing `SCOOP_GLOBAL`
```powershell
$env:SCOOP_GLOBAL='F:\GlobalScoopApps'
[Environment]::SetEnvironmentVariable('SCOOP_GLOBAL', $env:SCOOP_GLOBAL, 'Machine')
# run the installer
```
## [Documentation](https://github.com/ScoopInstaller/Scoop/wiki)
## Multi-connection downloads with `aria2`
Scoop can utilize [`aria2`](https://github.com/aria2/aria2) to use multi-connection downloads. Simply install `aria2` through Scoop and it will be used for all downloads afterward.
```powershell
scoop install aria2
```
By default, `scoop` displays a warning when running `scoop install` or `scoop update` while `aria2` is enabled. This warning can be suppressed by running `scoop config aria2-warning-enabled false`.
You can tweak the following `aria2` settings with the `scoop config` command:
- aria2-enabled (default: true)
- aria2-warning-enabled (default: true)
- [aria2-retry-wait](https://aria2.github.io/manual/en/html/aria2c.html#cmdoption-retry-wait) (default: 2)
- [aria2-split](https://aria2.github.io/manual/en/html/aria2c.html#cmdoption-s) (default: 5)
- [aria2-max-connection-per-server](https://aria2.github.io/manual/en/html/aria2c.html#cmdoption-x) (default: 5)
- [aria2-min-split-size](https://aria2.github.io/manual/en/html/aria2c.html#cmdoption-k) (default: 5M)
## Inspiration
- [Homebrew](http://mxcl.github.io/homebrew/)
- [sub](https://github.com/37signals/sub#readme)
## What sort of apps can Scoop install?
The apps that install best with Scoop are commonly called "portable" apps: i.e. compressed program files that run stand-alone when extracted and don't have side-effects like changing the registry or putting files outside the program directory.
Since installers are common, Scoop supports them too (and their uninstallers).
Scoop is also great at handling single-file programs and Powershell scripts. These don't even need to be compressed. See the [runat](https://github.com/ScoopInstaller/Main/blob/master/bucket/runat.json) package for an example: it's really just a GitHub gist.
### Support this project
If you find Scoop useful and would like to support ongoing development and maintenance, here's how:
- [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DM2SUH9EUXSKJ) (one-time donation)
## Known application buckets
The following buckets are known to scoop:
- [main](https://github.com/ScoopInstaller/Main) - Default bucket for the most common (mostly CLI) apps
- [extras](https://github.com/ScoopInstaller/Extras) - Apps that don't fit the main bucket's [criteria](https://github.com/ScoopInstaller/Scoop/wiki/Criteria-for-including-apps-in-the-main-bucket)
- [games](https://github.com/Calinou/scoop-games) - Open source/freeware games and game-related tools
- [nerd-fonts](https://github.com/matthewjberger/scoop-nerd-fonts) - Nerd Fonts
- [nirsoft](https://github.com/kodybrown/scoop-nirsoft) - Almost all of the [250+](https://rasa.github.io/scoop-directory/by-apps#kodybrown_scoop-nirsoft) apps from [Nirsoft](https://nirsoft.net)
- [java](https://github.com/ScoopInstaller/Java) - A collection of Java development kits (JDKs), Java runtime engines (JREs), Java's virtual machine debugging tools and Java based runtime engines.
- [jetbrains](https://github.com/Ash258/Scoop-JetBrains) - Installers for all JetBrains utilities and IDEs
<!-- * [nightlies](https://github.com/ScoopInstaller/Nightlies) - No longer used -->
- [nonportable](https://github.com/TheRandomLabs/scoop-nonportable) - Non-portable apps (may require UAC)
- [php](https://github.com/ScoopInstaller/PHP) - Installers for most versions of PHP
- [versions](https://github.com/ScoopInstaller/Versions) - Alternative versions of apps found in other buckets
The main bucket is installed by default. To add any of the other buckets, type:
```
scoop bucket add bucketname
```
For example, to add the extras bucket, type:
```
scoop bucket add extras
```
## Other application buckets
Many other application buckets hosted on Github can be found in the [Scoop Directory](https://github.com/rasa/scoop-directory).
version: "{build}-{branch}"
branches:
except:
- gh-pages
build: off
deploy: off
clone_depth: 49
image: Visual Studio 2019
environment:
scoop: C:\projects\scoop
scoop_home: C:\projects\scoop
scoop_helpers: C:\projects\helpers
lessmsi: '%scoop_helpers%\lessmsi\lessmsi.exe'
innounp: '%scoop_helpers%\innounp\innounp.exe'
zstd: '%scoop_helpers%\zstd\zstd.exe'
matrix:
- PowerShell: 5
- PowerShell: 6
cache:
- '%USERPROFILE%\Documents\WindowsPowerShell\Modules -> appveyor.yml, test\bin\*.ps1'
- C:\projects\helpers -> appveyor.yml, test\bin\*.ps1
matrix:
fast_finish: true
for:
- matrix:
only:
- PowerShell: 5
install:
- ps: . "$env:SCOOP_HOME\test\bin\init.ps1"
test_script:
- ps: . "$env:SCOOP_HOME\test\bin\test.ps1" -TestPath "$env:APPVEYOR_BUILD_FOLDER"
- matrix:
only:
- PowerShell: 6
install:
- pwsh: . "$env:SCOOP_HOME\test\bin\init.ps1"
test_script:
- pwsh: . "$env:SCOOP_HOME\test\bin\test.ps1" -TestPath "$env:APPVEYOR_BUILD_FOLDER"
<#
.SYNOPSIS
Updates manifests and pushes them or creates pull-requests.
.DESCRIPTION
Updates manifests and pushes them directly to the master branch or creates pull-requests for upstream.
.PARAMETER Upstream
Upstream repository with the target branch.
Must be in format '<user>/<repo>:<branch>'
.PARAMETER App
Manifest name to search.
Placeholders are supported.
.PARAMETER Dir
The directory where to search for manifests.
.PARAMETER Push
Push updates directly to 'origin master'.
.PARAMETER Request
Create pull-requests on 'upstream master' for each update.
.PARAMETER Help
Print help to console.
.PARAMETER SpecialSnowflakes
An array of manifests, which should be updated all the time. (-ForceUpdate parameter to checkver)
.PARAMETER SkipUpdated
Updated manifests will not be shown.
.EXAMPLE
PS BUCKETROOT > .\bin\auto-pr.ps1 'someUsername/repository:branch' -Request
.EXAMPLE
PS BUCKETROOT > .\bin\auto-pr.ps1 -Push
Update all manifests inside 'bucket/' directory.
#>
param(
[Parameter(Mandatory = $true)]
[ValidateScript( {
if (!($_ -match '^(.*)\/(.*):(.*)$')) {
throw 'Upstream must be in this format: <user>/<repo>:<branch>'
}
$true
})]
[String] $Upstream,
[String] $App = '*',
[Parameter(Mandatory = $true)]
[ValidateScript( {
if (!(Test-Path $_ -Type Container)) {
throw "$_ is not a directory!"
} else {
$true
}
})]
[String] $Dir,
[Switch] $Push,
[Switch] $Request,
[Switch] $Help,
[string[]] $SpecialSnowflakes,
[Switch] $SkipUpdated
)
. "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\json.ps1"
. "$PSScriptRoot\..\lib\unix.ps1"
$Dir = Resolve-Path $Dir
if ((!$Push -and !$Request) -or $Help) {
Write-Host @'
Usage: auto-pr.ps1 [OPTION]
Mandatory options:
-p, -push push updates directly to 'origin master'
-r, -request create pull-requests on 'upstream master' for each update
Optional options:
-u, -upstream upstream repository with target branch
only used if -r is set (default: lukesampson/scoop:master)
-h, -help
'@
exit 0
}
if (is_unix) {
if (!(which hub)) {
Write-Host "Please install hub ('brew install hub' or visit: https://hub.github.com/)" -ForegroundColor Yellow
exit 1
}
} else {
if (!(scoop which hub)) {
Write-Host "Please install hub 'scoop install hub'" -ForegroundColor Yellow
exit 1
}
}
function execute($cmd) {
Write-Host $cmd -ForegroundColor Green
$output = Invoke-Expression $cmd
if ($LASTEXITCODE -gt 0) {
abort "^^^ Error! See above ^^^ (last command: $cmd)"
}
return $output
}
function pull_requests($json, [String] $app, [String] $upstream, [String] $manifest) {
$version = $json.version
$homepage = $json.homepage
$branch = "manifest/$app-$version"
execute 'hub checkout master'
Write-Host "hub rev-parse --verify $branch" -ForegroundColor Green
hub rev-parse --verify $branch
if ($LASTEXITCODE -eq 0) {
Write-Host "Skipping update $app ($version) ..." -ForegroundColor Yellow
return
}
Write-Host "Creating update $app ($version) ..." -ForegroundColor DarkCyan
execute "hub checkout -b $branch"
execute "hub add $manifest"
execute "hub commit -m '${app}: Update to version $version'"
Write-Host "Pushing update $app ($version) ..." -ForegroundColor DarkCyan
execute "hub push origin $branch"
if ($LASTEXITCODE -gt 0) {
error "Push failed! (hub push origin $branch)"
execute 'hub reset'
return
}
Start-Sleep 1
Write-Host "Pull-Request update $app ($version) ..." -ForegroundColor DarkCyan
Write-Host "hub pull-request -m '<msg>' -b '$upstream' -h '$branch'" -ForegroundColor Green
$msg = @"
$app`: Update to version $version
Hello lovely humans,
a new version of [$app]($homepage) is available.
| State | Update :rocket: |
| :---------- | :-------------- |
| New version | $version |
"@
hub pull-request -m "$msg" -b '$upstream' -h '$branch'
if ($LASTEXITCODE -gt 0) {
execute 'hub reset'
abort "Pull Request failed! (hub pull-request -m '${app}: Update to version $version' -b '$upstream' -h '$branch')"
}
}
Write-Host 'Updating ...' -ForegroundColor DarkCyan
if ($Push) {
execute 'hub pull origin master'
execute 'hub checkout master'
} else {
execute 'hub pull upstream master'
execute 'hub push origin master'
}
. "$PSScriptRoot\checkver.ps1" -App $App -Dir $Dir -Update -SkipUpdated:$SkipUpdated
if ($SpecialSnowflakes) {
Write-Host "Forcing update on our special snowflakes: $($SpecialSnowflakes -join ',')" -ForegroundColor DarkCyan
$SpecialSnowflakes -split ',' | ForEach-Object {
. "$PSScriptRoot\checkver.ps1" $_ -Dir $Dir -ForceUpdate
}
}
hub diff --name-only | ForEach-Object {
$manifest = $_
if (!$manifest.EndsWith('.json')) {
return
}
$app = ([System.IO.Path]::GetFileNameWithoutExtension($manifest))
$json = parse_json $manifest
if (!$json.version) {
error "Invalid manifest: $manifest ..."
return
}
$version = $json.version
if ($Push) {
Write-Host "Creating update $app ($version) ..." -ForegroundColor DarkCyan
execute "hub add $manifest"
# detect if file was staged, because it's not when only LF or CRLF have changed
$status = execute 'hub status --porcelain -uno'
$status = $status | Where-Object { $_ -match "M\s{2}.*$app.json" }
if ($status -and $status.StartsWith('M ') -and $status.EndsWith("$app.json")) {
execute "hub commit -m '${app}: Update to version $version'"
} else {
Write-Host "Skipping $app because only LF/CRLF changes were detected ..." -ForegroundColor Yellow
}
} else {
pull_requests $json $app $Upstream $manifest
}
}
if ($Push) {
Write-Host 'Pushing updates ...' -ForegroundColor DarkCyan
execute 'hub push origin master'
} else {
Write-Host 'Returning to master branch and removing unstaged files ...' -ForegroundColor DarkCyan
execute 'hub checkout -f master'
}
execute 'hub reset --hard'
<#
.SYNOPSIS
Check if ALL urls inside manifest have correct hashes.
.PARAMETER App
Manifest to be checked.
Wildcard is supported.
.PARAMETER Dir
Where to search for manifest(s).
.PARAMETER Update
When there are mismatched hashes, manifest will be updated.
.PARAMETER ForceUpdate
Manifest will be updated all the time. Not only when there are mismatched hashes.
.PARAMETER SkipCorrect
Manifests without mismatch will not be shown.
.PARAMETER UseCache
Downloaded files will not be deleted after script finish.
Should not be used, because check should be used for downloading actual version of file (as normal user, not finding in some document from vendors, which could be damaged / wrong (Example: Slack@3.3.1 lukesampson/scoop-extras#1192)), not some previously downloaded.
.EXAMPLE
PS BUCKETROOT> .\bin\checkhashes.ps1
Check all manifests for hash mismatch.
.EXAMPLE
PS BUCKETROOT> .\bin\checkhashes.ps1 MANIFEST -Update
Check MANIFEST and Update if there are some wrong hashes.
#>
param(
[String] $App = '*',
[Parameter(Mandatory = $true)]
[ValidateScript( {
if (!(Test-Path $_ -Type Container)) {
throw "$_ is not a directory!"
} else {
$true
}
})]
[String] $Dir,
[Switch] $Update,
[Switch] $ForceUpdate,
[Switch] $SkipCorrect,
[Alias('k')]
[Switch] $UseCache
)
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\buckets.ps1"
. "$PSScriptRoot\..\lib\autoupdate.ps1"
. "$PSScriptRoot\..\lib\json.ps1"
. "$PSScriptRoot\..\lib\versions.ps1"
. "$PSScriptRoot\..\lib\install.ps1"
. "$PSScriptRoot\..\lib\unix.ps1"
$Dir = Resolve-Path $Dir
if ($ForceUpdate) { $Update = $true }
# Cleanup
if (!$UseCache) { Remove-Item "$cachedir\*HASH_CHECK*" -Force }
function err ([String] $name, [String[]] $message) {
Write-Host "$name`: " -ForegroundColor Red -NoNewline
Write-Host ($message -join "`r`n") -ForegroundColor Red
}
$MANIFESTS = @()
foreach ($single in Get-ChildItem $Dir "$App.json") {
$name = (strip_ext $single.Name)
$manifest = parse_json "$Dir\$($single.Name)"
# Skip nighly manifests, since their hash validation is skipped
if ($manifest.version -eq 'nightly') { continue }
$urls = @()
$hashes = @()
if ($manifest.url) {
$manifest.url | ForEach-Object { $urls += $_ }
$manifest.hash | ForEach-Object { $hashes += $_ }
} elseif ($manifest.architecture) {
# First handle 64bit
script:url $manifest '64bit' | ForEach-Object { $urls += $_ }
hash $manifest '64bit' | ForEach-Object { $hashes += $_ }
script:url $manifest '32bit' | ForEach-Object { $urls += $_ }
hash $manifest '32bit' | ForEach-Object { $hashes += $_ }
} else {
err $name 'Manifest does not contain URL property.'
continue
}
# Number of URLS and Hashes is different
if ($urls.Length -ne $hashes.Length) {
err $name 'URLS and hashes count mismatch.'
continue
}
$MANIFESTS += @{
app = $name
manifest = $manifest
urls = $urls
hashes = $hashes
}
}
# clear any existing events
Get-Event | ForEach-Object { Remove-Event $_.SourceIdentifier }
foreach ($current in $MANIFESTS) {
$count = 0
# Array of indexes mismatched hashes.
$mismatched = @()
# Array of computed hashes
$actuals = @()
$current.urls | ForEach-Object {
$algorithm, $expected = get_hash $current.hashes[$count]
$version = 'HASH_CHECK'
$tmp = $expected_hash -split ':'
dl_with_cache $current.app $version $_ $null $null -use_cache:$UseCache
$to_check = fullpath (cache_path $current.app $version $_)
$actual_hash = compute_hash $to_check $algorithm
# Append type of algorithm to both expected and actual if it's not sha256
if ($algorithm -ne 'sha256') {
$actual_hash = "$algorithm`:$actual_hash"
$expected = "$algorithm`:$expected"
}
$actuals += $actual_hash
if ($actual_hash -ne $expected) {
$mismatched += $count
}
$count++
}
if ($mismatched.Length -eq 0 ) {
if (!$SkipCorrect) {
Write-Host "$($current.app): " -NoNewline
Write-Host 'OK' -ForegroundColor Green
}
} else {
Write-Host "$($current.app): " -NoNewline
Write-Host 'Mismatch found ' -ForegroundColor Red
$mismatched | ForEach-Object {
$file = fullpath (cache_path $current.app $version $current.urls[$_])
Write-Host "`tURL:`t`t$($current.urls[$_])"
if (Test-Path $file) {
Write-Host "`tFirst bytes:`t$((get_magic_bytes_pretty $file ' ').ToUpper())"
}
Write-Host "`tExpected:`t$($current.hashes[$_])" -ForegroundColor Green
Write-Host "`tActual:`t`t$($actuals[$_])" -ForegroundColor Red
}
}
if ($Update) {
if ($current.manifest.url -and $current.manifest.hash) {
$current.manifest.hash = $actuals
} else {
$platforms = ($current.manifest.architecture | Get-Member -MemberType NoteProperty).Name
# Defaults to zero, don't know, which architecture is available
$64bit_count = 0
$32bit_count = 0
if ($platforms.Contains('64bit')) {
$64bit_count = $current.manifest.architecture.'64bit'.hash.Count
# 64bit is get, donwloaded and added first
$current.manifest.architecture.'64bit'.hash = $actuals[0..($64bit_count - 1)]
}
if ($platforms.Contains('32bit')) {
$32bit_count = $current.manifest.architecture.'32bit'.hash.Count
$max = $64bit_count + $32bit_count - 1 # Edge case if manifest contains 64bit and 32bit.
$current.manifest.architecture.'32bit'.hash = $actuals[($64bit_count)..$max]
}
}
Write-Host "Writing updated $($current.app) manifest" -ForegroundColor DarkGreen
$current.manifest = $current.manifest | ConvertToPrettyJson
$path = Resolve-Path "$Dir\$($current.app).json"
[System.IO.File]::WriteAllLines($path, $current.manifest)
}
}
<#
.SYNOPSIS
List manifests which do not have valid URLs.
.PARAMETER App
Manifest name to search.
Placeholder is supported.
.PARAMETER Dir
Where to search for manifest(s).
.PARAMETER Timeout
How long (seconds) the request can be pending before it times out.
.PARAMETER SkipValid
Manifests will all valid URLs will not be shown.
#>
param(
[String] $App = '*',
[Parameter(Mandatory = $true)]
[ValidateScript( {
if (!(Test-Path $_ -Type Container)) {
throw "$_ is not a directory!"
} else {
$true
}
})]
[String] $Dir,
[Int] $Timeout = 5,
[Switch] $SkipValid
)
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\install.ps1"
$Dir = Resolve-Path $Dir
$Queue = @()
Get-ChildItem $Dir "$App.json" | ForEach-Object {
$manifest = parse_json "$Dir\$($_.Name)"
$Queue += , @($_.Name, $manifest)
}
Write-Host '[' -NoNewLine
Write-Host 'U' -NoNewLine -ForegroundColor Cyan
Write-Host ']RLs'
Write-Host ' | [' -NoNewLine
Write-Host 'O' -NoNewLine -ForegroundColor Green
Write-Host ']kay'
Write-Host ' | | [' -NoNewLine
Write-Host 'F' -NoNewLine -ForegroundColor Red
Write-Host ']ailed'
Write-Host ' | | |'
function test_dl([String] $url, $cookies) {
# Trim renaming suffix, prevent getting 40x response
$url = ($url -split '#/')[0]
$wreq = [Net.WebRequest]::Create($url)
$wreq.Timeout = $Timeout * 1000
if ($wreq -is [Net.HttpWebRequest]) {
$wreq.UserAgent = Get-UserAgent
$wreq.Referer = strip_filename $url
if ($cookies) {
$wreq.Headers.Add('Cookie', (cookie_header $cookies))
}
}
$wres = $null
try {
$wres = $wreq.GetResponse()
return $url, $wres.StatusCode, $null
} catch {
$e = $_.Exception
if ($e.InnerException) { $e = $e.InnerException }
return $url, 'Error', $e.Message
} finally {
if ($null -ne $wres -and $wres -isnot [Net.FtpWebResponse]) {
$wres.Close()
}
}
}
foreach ($man in $Queue) {
$name, $manifest = $man
$urls = @()
$ok = 0
$failed = 0
$errors = @()
if ($manifest.url) {
$manifest.url | ForEach-Object { $urls += $_ }
} else {
script:url $manifest '64bit' | ForEach-Object { $urls += $_ }
script:url $manifest '32bit' | ForEach-Object { $urls += $_ }
}
$urls | ForEach-Object {
$url, $status, $msg = test_dl $_ $manifest.cookie
if ($msg) { $errors += "$msg ($url)" }
if ($status -eq 'OK' -or $status -eq 'OpeningData') { $ok += 1 } else { $failed += 1 }
}
if (($ok -eq $urls.Length) -and $SkipValid) { continue }
# URLS
Write-Host '[' -NoNewLine
Write-Host $urls.Length -NoNewLine -ForegroundColor Cyan
Write-Host ']' -NoNewLine
# Okay
Write-Host '[' -NoNewLine
if ($ok -eq $urls.Length) {
Write-Host $ok -NoNewLine -ForegroundColor Green
} elseif ($ok -eq 0) {
Write-Host $ok -NoNewLine -ForegroundColor Red
} else {
Write-Host $ok -NoNewLine -ForegroundColor Yellow
}
Write-Host ']' -NoNewLine
# Failed
Write-Host '[' -NoNewLine
if ($failed -eq 0) {
Write-Host $failed -NoNewLine -ForegroundColor Green
} else {
Write-Host $failed -NoNewLine -ForegroundColor Red
}
Write-Host '] ' -NoNewLine
Write-Host (strip_ext $name)
$errors | ForEach-Object {
Write-Host " > $_" -ForegroundColor DarkRed
}
}
<#
.SYNOPSIS
Check manifest for a newer version.
.DESCRIPTION
Checks websites for newer versions using an (optional) regular expression defined in the manifest.
.PARAMETER App
Manifest name to search.
Placeholders are supported.
.PARAMETER Dir
Where to search for manifest(s).
.PARAMETER Update
Update given manifest
.PARAMETER ForceUpdate
Update given manifest(s) even when there is no new version.
Useful for hash updates.
.PARAMETER SkipUpdated
Updated manifests will not be shown.
.EXAMPLE
PS BUCKETROOT > .\bin\checkver.ps1
Check all manifest inside default directory.
.EXAMPLE
PS BUCKETROOT > .\bin\checkver.ps1 -SkipUpdated
Check all manifest inside default directory (list only outdated manifests).
.EXAMPLE
PS BUCKETROOT > .\bin\checkver.ps1 -Update
Check all manifests and update All outdated manifests.
.EXAMPLE
PS BUCKETROOT > .\bin\checkver.ps1 APP
Check manifest APP.json inside default directory.
.EXAMPLE
PS BUCKETROOT > .\bin\checkver.ps1 APP -Update
Check manifest APP.json and update, if there is newer version.
.EXAMPLE
PS BUCKETROOT > .\bin\checkver.ps1 APP -ForceUpdate
Check manifest APP.json and update, even if there is no new version.
.EXAMPLE
PS BUCKETROOT > .\bin\checkver.ps1 APP -Update -Version VER
Check manifest APP.json and update, using version VER
.EXAMPLE
PS BUCKETROOT > .\bin\checkver.ps1 APP DIR
Check manifest APP.json inside ./DIR directory.
.EXAMPLE
PS BUCKETROOT > .\bin\checkver.ps1 -Dir DIR
Check all manifests inside ./DIR directory.
.EXAMPLE
PS BUCKETROOT > .\bin\checkver.ps1 APP DIR -Update
Check manifest APP.json inside ./DIR directory and update if there is newer version.
#>
param(
[String] $App = '*',
[Parameter(Mandatory = $true)]
[ValidateScript( {
if (!(Test-Path $_ -Type Container)) {
throw "$_ is not a directory!"
} else {
$true
}
})]
[String] $Dir,
[Switch] $Update,
[Switch] $ForceUpdate,
[Switch] $SkipUpdated,
[String] $Version = ''
)
. "$psscriptroot\..\lib\core.ps1"
. "$psscriptroot\..\lib\manifest.ps1"
. "$psscriptroot\..\lib\buckets.ps1"
. "$psscriptroot\..\lib\autoupdate.ps1"
. "$psscriptroot\..\lib\json.ps1"
. "$psscriptroot\..\lib\versions.ps1"
. "$psscriptroot\..\lib\install.ps1" # needed for hash generation
. "$psscriptroot\..\lib\unix.ps1"
$Dir = Resolve-Path $Dir
$Search = $App
# get apps to check
$Queue = @()
$json = ''
Get-ChildItem $Dir "$App.json" | ForEach-Object {
$json = parse_json "$Dir\$($_.Name)"
if ($json.checkver) {
$Queue += , @($_.Name, $json)
}
}
# clear any existing events
Get-Event | ForEach-Object {
Remove-Event $_.SourceIdentifier
}
# start all downloads
$Queue | ForEach-Object {
$name, $json = $_
$substitutions = Get-VersionSubstitution $json.version
$wc = New-Object Net.Webclient
if ($json.checkver.useragent) {
$wc.Headers.Add('User-Agent', (substitute $json.checkver.useragent $substitutions))
} else {
$wc.Headers.Add('User-Agent', (Get-UserAgent))
}
Register-ObjectEvent $wc downloadstringcompleted -ErrorAction Stop | Out-Null
$githubRegex = '\/releases\/tag\/(?:v|V)?([\d.]+)'
$url = $json.homepage
if ($json.checkver.url) {
$url = $json.checkver.url
}
$regex = ''
$jsonpath = ''
$xpath = ''
$replace = ''
if ($json.checkver -eq 'github') {
if (!$json.homepage.StartsWith('https://github.com/')) {
error "$name checkver expects the homepage to be a github repository"
}
$url = $json.homepage + '/releases/latest'
$regex = $githubRegex
}
if ($json.checkver.github) {
$url = $json.checkver.github + '/releases/latest'
$regex = $githubRegex
}
if ($json.checkver.re) {
$regex = $json.checkver.re
}
if ($json.checkver.regex) {
$regex = $json.checkver.regex
}
if ($json.checkver.jp) {
$jsonpath = $json.checkver.jp
}
if ($json.checkver.jsonpath) {
$jsonpath = $json.checkver.jsonpath
}
if ($json.checkver.xpath) {
$xpath = $json.checkver.xpath
}
if ($json.checkver.replace -and $json.checkver.replace.GetType() -eq [System.String]) {
$replace = $json.checkver.replace
}
if (!$jsonpath -and !$regex -and !$xpath) {
$regex = $json.checkver
}
$reverse = $json.checkver.reverse -and $json.checkver.reverse -eq 'true'
$url = substitute $url $substitutions
$state = New-Object psobject @{
app = (strip_ext $name);
url = $url;
regex = $regex;
json = $json;
jsonpath = $jsonpath;
xpath = $xpath;
reverse = $reverse;
replace = $replace;
}
$wc.Headers.Add('Referer', (strip_filename $url))
$wc.DownloadStringAsync($url, $state)
}
function next($er) {
Write-Host "$App`: " -NoNewline
Write-Host $er -ForegroundColor DarkRed
}
# wait for all to complete
$in_progress = $Queue.length
while ($in_progress -gt 0) {
$ev = Wait-Event
Remove-Event $ev.SourceIdentifier
$in_progress--
$state = $ev.SourceEventArgs.UserState
$app = $state.app
$json = $state.json
$url = $state.url
$regexp = $state.regex
$jsonpath = $state.jsonpath
$xpath = $state.xpath
$reverse = $state.reverse
$replace = $state.replace
$expected_ver = $json.version
$ver = ''
$page = $ev.SourceEventArgs.Result
$err = $ev.SourceEventArgs.Error
if ($json.checkver.script) {
$page = $json.checkver.script -join "`r`n" | Invoke-Expression
}
if ($err) {
next "$($err.message)`r`nURL $url is not valid"
continue
}
if (!$regex -and $replace) {
next "'replace' requires 're' or 'regex'"
continue
}
if ($jsonpath) {
$ver = json_path $page $jsonpath
if (!$ver) {
$ver = json_path_legacy $page $jsonpath
}
if (!$ver) {
next "couldn't find '$jsonpath' in $url"
continue
}
}
if ($xpath) {
$xml = [xml]$page
# Find all `significant namespace declarations` from the XML file
$nsList = $xml.SelectNodes("//namespace::*[not(. = ../../namespace::*)]")
# Then add them into the NamespaceManager
$nsmgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsList | ForEach-Object {
$nsmgr.AddNamespace($_.LocalName, $_.Value)
}
# Getting version from XML, using XPath
$ver = $xml.SelectSingleNode($xpath, $nsmgr).'#text'
if (!$ver) {
next "couldn't find '$xpath' in $url"
continue
}
}
if ($jsonpath -and $regexp) {
$page = $ver
$ver = ''
}
if ($xpath -and $regexp) {
$page = $ver
$ver = ''
}
if ($regexp) {
$regex = New-Object System.Text.RegularExpressions.Regex($regexp)
if ($reverse) {
$match = $regex.Matches($page) | Select-Object -Last 1
} else {
$match = $regex.Matches($page) | Select-Object -First 1
}
if ($match -and $match.Success) {
$matchesHashtable = @{}
$regex.GetGroupNames() | ForEach-Object { $matchesHashtable.Add($_, $match.Groups[$_].Value) }
$ver = $matchesHashtable['1']
if ($replace) {
$ver = $regex.Replace($match.Value, $replace)
}
if (!$ver) {
$ver = $matchesHashtable['version']
}
} else {
next "couldn't match '$regexp' in $url"
continue
}
}
if (!$ver) {
next "couldn't find new version in $url"
continue
}
# Skip actual only if versions are same and there is no -f
if (($ver -eq $expected_ver) -and !$ForceUpdate -and $SkipUpdated) { continue }
Write-Host "$App`: " -NoNewline
# version hasn't changed (step over if forced update)
if ($ver -eq $expected_ver -and !$ForceUpdate) {
Write-Host $ver -ForegroundColor DarkGreen
continue
}
Write-Host $ver -ForegroundColor DarkRed -NoNewline
Write-Host " (scoop version is $expected_ver)" -NoNewline
$update_available = (Compare-Version -ReferenceVersion $ver -DifferenceVersion $expected_ver) -ne 0
if ($json.autoupdate -and $update_available) {
Write-Host ' autoupdate available' -ForegroundColor Cyan
} else {
Write-Host ''
}
# forcing an update implies updating, right?
if ($ForceUpdate) { $Update = $true }
if ($Update -and $json.autoupdate) {
if ($ForceUpdate) {
Write-Host 'Forcing autoupdate!' -ForegroundColor DarkMagenta
}
try {
if ($Version -ne "") {
$ver = $Version
}
Invoke-AutoUpdate $App $Dir $json $ver $matchesHashtable
} catch {
error $_.Exception.Message
}
}
}
<#
.SYNOPSIS
Search for application description on homepage.
.PARAMETER App
Manifest name to search.
Placeholders are supported.
.PARAMETER Dir
Where to search for manifest(s).
#>
param(
[String] $App = '*',
[Parameter(Mandatory = $true)]
[ValidateScript( {
if (!(Test-Path $_ -Type Container)) {
throw "$_ is not a directory!"
} else {
$true
}
})]
[String] $Dir
)
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\description.ps1"
$Dir = Resolve-Path $Dir
$Queue = @()
Get-ChildItem $Dir "$App.json" | ForEach-Object {
$manifest = parse_json "$Dir\$($_.Name)"
$Queue += , @(($_.Name -replace '\.json$', ''), $manifest)
}
$Queue | ForEach-Object {
$name, $manifest = $_
Write-Host "$name`: " -NoNewline
if (!$manifest.homepage) {
Write-Host "`nNo homepage set." -ForegroundColor Red
return
}
# get description from homepage
try {
$wc = New-Object Net.Webclient
$wc.Headers.Add('User-Agent', (Get-UserAgent))
$home_html = $wc.DownloadString($manifest.homepage)
} catch {
Write-Host "`n$($_.Exception.Message)" -ForegroundColor Red
return
}
$description, $descr_method = find_description $manifest.homepage $home_html
if (!$description) {
Write-Host "`nDescription not found ($($manifest.homepage))" -ForegroundColor Red
return
}
$description = clean_description $description
Write-Host "(found by $descr_method)"
Write-Host " ""$description""" -ForegroundColor Green
}
<#
.SYNOPSIS
Format manifest.
.PARAMETER App
Manifest to format.
Wildcards are supported.
.PARAMETER Dir
Where to search for manifest(s).
.EXAMPLE
PS BUCKETROOT> .\bin\formatjson.ps1
Format all manifests inside bucket directory.
.EXAMPLE
PS BUCKETROOT> .\bin\formatjson.ps1 7zip
Format manifest '7zip' inside bucket directory.
#>
param(
[String] $App = '*',
[Parameter(Mandatory = $true)]
[ValidateScript( {
if (!(Test-Path $_ -Type Container)) {
throw "$_ is not a directory!"
} else {
$true
}
})]
[String] $Dir
)
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\json.ps1"
$Dir = Resolve-Path $Dir
Get-ChildItem $Dir "$App.json" | ForEach-Object {
if ($PSVersionTable.PSVersion.Major -gt 5) { $_ = $_.Name } # Fix for pwsh
# beautify
$json = parse_json "$Dir\$_" | ConvertToPrettyJson
# convert to 4 spaces
$json = $json -replace "`t", ' '
[System.IO.File]::WriteAllLines("$Dir\$_", $json)
}
#Requires -Version 5
# remote install:
# Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
$old_erroractionpreference = $erroractionpreference
$erroractionpreference = 'stop' # quit if anything goes wrong
if (($PSVersionTable.PSVersion.Major) -lt 5) {
Write-Output "PowerShell 5 or later is required to run Scoop."
Write-Output "Upgrade PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/setup/installing-windows-powershell"
break
}
# show notification to change execution policy:
$allowedExecutionPolicy = @('Unrestricted', 'RemoteSigned', 'ByPass')
if ((Get-ExecutionPolicy).ToString() -notin $allowedExecutionPolicy) {
Write-Output "PowerShell requires an execution policy in [$($allowedExecutionPolicy -join ", ")] to run Scoop."
Write-Output "For example, to set the execution policy to 'RemoteSigned' please run :"
Write-Output "'Set-ExecutionPolicy RemoteSigned -scope CurrentUser'"
break
}
if ([System.Enum]::GetNames([System.Net.SecurityProtocolType]) -notcontains 'Tls12') {
Write-Output "Scoop requires at least .NET Framework 4.5"
Write-Output "Please download and install it first:"
Write-Output "https://www.microsoft.com/net/download"
break
}
# get core functions
$core_url = 'https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/lib/core.ps1'
Write-Output 'Initializing...'
Invoke-Expression (new-object net.webclient).downloadstring($core_url)
# prep
if (installed 'scoop') {
write-host "Scoop is already installed. Run 'scoop update' to get the latest version." -f red
# don't abort if invoked with iex that would close the PS session
if ($myinvocation.mycommand.commandtype -eq 'Script') { return } else { exit 1 }
}
$dir = ensure (versiondir 'scoop' 'current')
# download scoop zip
$zipurl = 'https://github.com/ScoopInstaller/Scoop/archive/master.zip'
$zipfile = "$dir\scoop.zip"
Write-Output 'Downloading scoop...'
dl $zipurl $zipfile
Write-Output 'Extracting...'
Add-Type -Assembly "System.IO.Compression.FileSystem"
[IO.Compression.ZipFile]::ExtractToDirectory($zipfile, "$dir\_tmp")
Copy-Item "$dir\_tmp\*master\*" $dir -Recurse -Force
Remove-Item "$dir\_tmp", $zipfile -Recurse -Force
Write-Output 'Creating shim...'
shim "$dir\bin\scoop.ps1" $false
# download main bucket
$dir = "$scoopdir\buckets\main"
$zipurl = 'https://github.com/ScoopInstaller/Main/archive/master.zip'
$zipfile = "$dir\main-bucket.zip"
Write-Output 'Downloading main bucket...'
New-Item $dir -Type Directory -Force | Out-Null
dl $zipurl $zipfile
Write-Output 'Extracting...'
[IO.Compression.ZipFile]::ExtractToDirectory($zipfile, "$dir\_tmp")
Copy-Item "$dir\_tmp\*-master\*" $dir -Recurse -Force
Remove-Item "$dir\_tmp", $zipfile -Recurse -Force
ensure_robocopy_in_path
ensure_scoop_in_path
scoop config lastupdate ([System.DateTime]::Now.ToString('o'))
success 'Scoop was installed successfully!'
Write-Output "Type 'scoop help' for instructions."
$erroractionpreference = $old_erroractionpreference # Reset $erroractionpreference to original value
<#
.SYNOPSIS
Check if manifest contains checkver and autoupdate property.
.PARAMETER App
Manifest name.
Wirldcard is supported.
.PARAMETER Dir
Location of manifests.
.PARAMETER SkipSupported
Manifests with checkver and autoupdate will not be presented.
#>
param(
[String] $App = '*',
[Parameter(Mandatory = $true)]
[ValidateScript( {
if (!(Test-Path $_ -Type Container)) {
throw "$_ is not a directory!"
} else {
$true
}
})]
[String] $Dir,
[Switch] $SkipSupported
)
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1"
$Dir = Resolve-Path $Dir
Write-Host '[' -NoNewLine
Write-Host 'C' -NoNewLine -ForegroundColor Green
Write-Host ']heckver'
Write-Host ' | [' -NoNewLine
Write-Host 'A' -NoNewLine -ForegroundColor Cyan
Write-Host ']utoupdate'
Write-Host ' | |'
Get-ChildItem $Dir "$App.json" | ForEach-Object {
$json = parse_json "$Dir\$($_.Name)"
if ($SkipSupported -and $json.checkver -and $json.autoupdate) { return }
Write-Host '[' -NoNewLine
Write-Host $(if ($json.checkver) { 'C' } else { ' ' }) -NoNewLine -ForegroundColor Green
Write-Host ']' -NoNewLine
Write-Host '[' -NoNewLine
Write-Host $(if ($json.autoupdate) { 'A' } else { ' ' }) -NoNewLine -ForegroundColor Cyan
Write-Host '] ' -NoNewLine
Write-Host (strip_ext $_.Name)
}
# for development, update the installed scripts to match local source
. "$psscriptroot\..\lib\core.ps1"
$src = relpath ".."
$dest = ensure (versiondir 'scoop' 'current')
# make sure not running from the installed directory
if("$src" -eq "$dest") { abort "$(strip_ext $myinvocation.mycommand.name) is for development only" }
'copying files...'
$output = robocopy $src $dest /mir /njh /njs /nfl /ndl /xd .git tmp /xf .DS_Store last_updated
$output | Where-Object { $_ -ne "" }
Write-Output 'creating shim...'
shim "$dest\bin\scoop.ps1" $false
ensure_scoop_in_path
success 'scoop was refreshed!'
#requires -v 3
param($cmd)
set-strictmode -off
. "$psscriptroot\..\lib\core.ps1"
. "$psscriptroot\..\lib\git.ps1"
. "$psscriptroot\..\lib\buckets.ps1"
. (relpath '..\lib\commands')
reset_aliases
$commands = commands
if ('--version' -contains $cmd -or (!$cmd -and '-v' -contains $args)) {
Push-Location $(versiondir 'scoop' 'current')
write-host "Current Scoop version:"
Invoke-Expression "git --no-pager log --oneline HEAD -n 1"
write-host ""
Pop-Location
Get-LocalBucket | ForEach-Object {
Push-Location (Find-BucketDirectory $_ -Root)
if(test-path '.git') {
write-host "'$_' bucket:"
Invoke-Expression "git --no-pager log --oneline HEAD -n 1"
write-host ""
}
Pop-Location
}
}
elseif (@($null, '--help', '/?') -contains $cmd -or $args[0] -contains '-h') { exec 'help' $args }
elseif ($commands -contains $cmd) { exec $cmd $args }
else { "scoop: '$cmd' isn't a scoop command. See 'scoop help'."; exit 1 }
invoke-pester "$psscriptroot\..\test"
<#
.SYNOPSIS
Uninstall ALL scoop applications and scoop itself.
.PARAMETER global
Global applications will be uninstalled.
.PARAMETER purge
Persisted data will be deleted.
#>
param(
[bool] $global,
[bool] $purge
)
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\install.ps1"
. "$PSScriptRoot\..\lib\shortcuts.ps1"
. "$PSScriptRoot\..\lib\versions.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1"
if ($global -and !(is_admin)) {
error 'You need admin rights to uninstall globally.'
exit 1
}
if ($purge) {
warn 'This will uninstall Scoop, all the programs that have been installed with Scoop and all persisted data!'
} else {
warn 'This will uninstall Scoop and all the programs that have been installed with Scoop!'
}
$yn = Read-Host 'Are you sure? (yN)'
if ($yn -notlike 'y*') { exit }
$errors = $false
# Uninstall given app
function do_uninstall($app, $global) {
$version = Select-CurrentVersion -AppName $app -Global:$global
$dir = versiondir $app $version $global
$manifest = installed_manifest $app $version $global
$install = install_info $app $version $global
$architecture = $install.architecture
Write-Output "Uninstalling '$app'"
run_uninstaller $manifest $architecture $dir
rm_shims $manifest $global $architecture
# If a junction was used during install, that will have been used
# as the reference directory. Othewise it will just be the version
# directory.
$refdir = unlink_current (appdir $app $global)
env_rm_path $manifest $refdir $global $architecture
env_rm $manifest $global $architecture
$appdir = appdir $app $global
try {
Remove-Item $appdir -Recurse -Force -ErrorAction Stop
} catch {
$errors = $true
warn "Couldn't remove $(friendly_path $appdir): $_.Exception"
}
}
function rm_dir($dir) {
try {
Remove-Item $dir -Recurse -Force -ErrorAction Stop
} catch {
abort "Couldn't remove $(friendly_path $dir): $_"
}
}
# Remove all folders (except persist) inside given scoop directory.
function keep_onlypersist($directory) {
Get-ChildItem $directory -Exclude 'persist' | ForEach-Object { rm_dir $_ }
}
# Run uninstallation for each app if necessary, continuing if there's
# a problem deleting a directory (which is quite likely)
if ($global) {
installed_apps $true | ForEach-Object { # global apps
do_uninstall $_ $true
}
}
installed_apps $false | ForEach-Object { # local apps
do_uninstall $_ $false
}
if ($errors) {
abort 'Not all apps could be deleted. Try again or restart.'
}
if ($purge) {
rm_dir $scoopdir
if ($global) { rm_dir $globaldir }
} else {
keep_onlypersist $scoopdir
if ($global) { keep_onlypersist $globaldir }
}
remove_from_path (shimdir $false)
if ($global) { remove_from_path (shimdir $true) }
success 'Scoop has been uninstalled.'
{
"main": "https://github.com/ScoopInstaller/Main",
"extras": "https://github.com/ScoopInstaller/Extras",
"versions": "https://github.com/ScoopInstaller/Versions",
"nightlies": "https://github.com/ScoopInstaller/Nightlies",
"nirsoft": "https://github.com/kodybrown/scoop-nirsoft",
"php": "https://github.com/ScoopInstaller/PHP",
"nerd-fonts": "https://github.com/matthewjberger/scoop-nerd-fonts",
"nonportable": "https://github.com/TheRandomLabs/scoop-nonportable",
"java": "https://github.com/ScoopInstaller/Java",
"games": "https://github.com/Calinou/scoop-games",
"jetbrains": "https://github.com/Ash258/Scoop-JetBrains"
}
此差异已折叠。
. "$PSScriptRoot\core.ps1"
$bucketsdir = "$scoopdir\buckets"
function Find-BucketDirectory {
<#
.DESCRIPTION
Return full path for bucket with given name.
Main bucket will be returned as default.
.PARAMETER Name
Name of bucket.
.PARAMETER Root
Root folder of bucket repository will be returned instead of 'bucket' subdirectory (if exists).
#>
param(
[string] $Name = 'main',
[switch] $Root
)
# Handle info passing empty string as bucket ($install.bucket)
if(($null -eq $Name) -or ($Name -eq '')) { $Name = 'main' }
$bucket = "$bucketsdir\$Name"
if ((Test-Path "$bucket\bucket") -and !$Root) {
$bucket = "$bucket\bucket"
}
return $bucket
}
function bucketdir($name) {
Show-DeprecatedWarning $MyInvocation 'Find-BucketDirectory'
return Find-BucketDirectory $name
}
function known_bucket_repos {
$json = "$PSScriptRoot\..\buckets.json"
return Get-Content $json -raw | convertfrom-json -ea stop
}
function known_bucket_repo($name) {
$buckets = known_bucket_repos
$buckets.$name
}
function known_buckets {
known_bucket_repos | ForEach-Object { $_.psobject.properties | Select-Object -expand 'name' }
}
function apps_in_bucket($dir) {
return Get-ChildItem $dir | Where-Object { $_.Name.endswith('.json') } | ForEach-Object { $_.Name -replace '.json$', '' }
}
function Get-LocalBucket {
<#
.SYNOPSIS
List all local buckets.
#>
return (Get-ChildItem -Directory $bucketsdir).Name
}
function buckets {
Show-DeprecatedWarning $MyInvocation 'Get-LocalBucket'
return Get-LocalBucket
}
function find_manifest($app, $bucket) {
if ($bucket) {
$manifest = manifest $app $bucket
if ($manifest) { return $manifest, $bucket }
return $null
}
foreach($bucket in Get-LocalBucket) {
$manifest = manifest $app $bucket
if($manifest) { return $manifest, $bucket }
}
}
function add_bucket($name, $repo) {
if (!$name) { "<name> missing"; $usage_add; exit 1 }
if (!$repo) {
$repo = known_bucket_repo $name
if (!$repo) { "Unknown bucket '$name'. Try specifying <repo>."; $usage_add; exit 1 }
}
if (!(Test-CommandAvailable git)) {
abort "Git is required for buckets. Run 'scoop install git' and try again."
}
$dir = Find-BucketDirectory $name -Root
if (test-path $dir) {
warn "The '$name' bucket already exists. Use 'scoop bucket rm $name' to remove it."
exit 0
}
write-host 'Checking repo... ' -nonewline
$out = git_ls_remote $repo 2>&1
if ($lastexitcode -ne 0) {
abort "'$repo' doesn't look like a valid git repository`n`nError given:`n$out"
}
write-host 'ok'
ensure $bucketsdir > $null
$dir = ensure $dir
git_clone "$repo" "`"$dir`"" -q
success "The $name bucket was added successfully."
}
function rm_bucket($name) {
if (!$name) { "<name> missing"; $usage_rm; exit 1 }
$dir = Find-BucketDirectory $name -Root
if (!(test-path $dir)) {
abort "'$name' bucket not found."
}
Remove-Item $dir -r -force -ea stop
}
function new_issue_msg($app, $bucket, $title, $body) {
$app, $manifest, $bucket, $url = Find-Manifest $app $bucket
$url = known_bucket_repo $bucket
$bucket_path = "$bucketsdir\$bucket"
if (Test-path $bucket_path) {
Push-Location $bucket_path
$remote = Invoke-Expression "git config --get remote.origin.url"
# Support ssh and http syntax
# git@PROVIDER:USER/REPO.git
# https://PROVIDER/USER/REPO.git
$remote -match '(@|:\/\/)(?<provider>.+)[:/](?<user>.*)\/(?<repo>.*)(\.git)?$' | Out-Null
$url = "https://$($Matches.Provider)/$($Matches.User)/$($Matches.Repo)"
Pop-Location
}
if(!$url) { return 'Please contact the bucket maintainer!' }
# Print only github repositories
if ($url -like '*github*') {
$title = [System.Web.HttpUtility]::UrlEncode("$app@$($manifest.version): $title")
$body = [System.Web.HttpUtility]::UrlEncode($body)
$url = $url -replace '\.git$', ''
$url = "$url/issues/new?title=$title"
if($body) {
$url += "&body=$body"
}
}
$msg = "`nPlease try again or create a new issue by using the following link and paste your console output:"
return "$msg`n$url"
}
function command_files {
(Get-ChildItem (relpath '..\libexec')) `
+ (Get-ChildItem "$scoopdir\shims") `
| Where-Object { $_.name -match 'scoop-.*?\.ps1$' }
}
function commands {
command_files | ForEach-Object { command_name $_ }
}
function command_name($filename) {
$filename.name | Select-String 'scoop-(.*?)\.ps1$' | ForEach-Object { $_.matches[0].groups[1].value }
}
function command_path($cmd) {
$cmd_path = relpath "..\libexec\scoop-$cmd.ps1"
# built in commands
if (!(Test-Path $cmd_path)) {
# get path from shim
$shim_path = "$scoopdir\shims\scoop-$cmd.ps1"
$line = ((Get-Content $shim_path) | Where-Object { $_.startswith('$path') })
if($line) {
Invoke-Expression -command "$line"
$cmd_path = $path
}
else { $cmd_path = $shim_path }
}
$cmd_path
}
function exec($cmd, $arguments) {
$cmd_path = command_path $cmd
& $cmd_path @arguments
}
此差异已折叠。
function Test-7zipRequirement {
[CmdletBinding(DefaultParameterSetName = "URL")]
[OutputType([Boolean])]
param (
[Parameter(Mandatory = $true, ParameterSetName = "URL")]
[String[]]
$URL,
[Parameter(Mandatory = $true, ParameterSetName = "File")]
[String]
$File
)
if ($URL) {
if ((get_config 7ZIPEXTRACT_USE_EXTERNAL)) {
return $false
} else {
return ($URL | Where-Object { Test-7zipRequirement -File $_ }).Count -gt 0
}
} else {
return $File -match '\.((gz)|(tar)|(t[abgpx]z2?)|(lzma)|(bz2?)|(7z)|(rar)|(iso)|(xz)|(lzh)|(nupkg))(\.[^.]+)?$'
}
}
function Test-ZstdRequirement {
[CmdletBinding(DefaultParameterSetName = "URL")]
[OutputType([Boolean])]
param (
[Parameter(Mandatory = $true, ParameterSetName = "URL")]
[String[]]
$URL,
[Parameter(Mandatory = $true, ParameterSetName = "File")]
[String]
$File
)
if ($URL) {
return ($URL | Where-Object { Test-ZstdRequirement -File $_ }).Count -gt 0
} else {
return $File -match '\.zst$'
}
}
function Test-LessmsiRequirement {
[CmdletBinding()]
[OutputType([Boolean])]
param (
[Parameter(Mandatory = $true)]
[String[]]
$URL
)
if ((get_config MSIEXTRACT_USE_LESSMSI)) {
return ($URL | Where-Object { $_ -match '\.msi$' }).Count -gt 0
} else {
return $false
}
}
function Expand-7zipArchive {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[String]
$Path,
[Parameter(Position = 1)]
[String]
$DestinationPath = (Split-Path $Path),
[String]
$ExtractDir,
[Parameter(ValueFromRemainingArguments = $true)]
[String]
$Switches,
[ValidateSet("All", "Skip", "Rename")]
[String]
$Overwrite,
[Switch]
$Removal
)
if ((get_config 7ZIPEXTRACT_USE_EXTERNAL)) {
try {
$7zPath = (Get-Command '7z' -CommandType Application | Select-Object -First 1).Source
} catch [System.Management.Automation.CommandNotFoundException] {
abort "Cannot find external 7-Zip (7z.exe) while '7ZIPEXTRACT_USE_EXTERNAL' is 'true'!`nRun 'scoop config 7ZIPEXTRACT_USE_EXTERNAL false' or install 7-Zip manually and try again."
}
} else {
$7zPath = Get-HelperPath -Helper 7zip
}
$LogPath = "$(Split-Path $Path)\7zip.log"
$ArgList = @('x', "`"$Path`"", "-o`"$DestinationPath`"", '-y')
$IsTar = ((strip_ext $Path) -match '\.tar$') -or ($Path -match '\.t[abgpx]z2?$')
if (!$IsTar -and $ExtractDir) {
$ArgList += "-ir!`"$ExtractDir\*`""
}
if ($Switches) {
$ArgList += (-split $Switches)
}
switch ($Overwrite) {
"All" { $ArgList += "-aoa" }
"Skip" { $ArgList += "-aos" }
"Rename" { $ArgList += "-aou" }
}
$Status = Invoke-ExternalCommand $7zPath $ArgList -LogPath $LogPath
if (!$Status) {
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
}
if (!$IsTar -and $ExtractDir) {
movedir "$DestinationPath\$ExtractDir" $DestinationPath | Out-Null
}
if (Test-Path $LogPath) {
Remove-Item $LogPath -Force
}
if ($IsTar) {
# Check for tar
$Status = Invoke-ExternalCommand $7zPath @('l', "`"$Path`"") -LogPath $LogPath
if ($Status) {
$TarFile = (Get-Content -Path $LogPath)[-4] -replace '.{53}(.*)', '$1' # get inner tar file name
Expand-7zipArchive -Path "$DestinationPath\$TarFile" -DestinationPath $DestinationPath -ExtractDir $ExtractDir -Removal
} else {
abort "Failed to list files in $Path.`nNot a 7-Zip supported archive file."
}
}
if ($Removal) {
# Remove original archive file
Remove-Item $Path -Force
}
}
function Expand-ZstdArchive {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[String]
$Path,
[Parameter(Position = 1)]
[String]
$DestinationPath = (Split-Path $Path),
[String]
$ExtractDir,
[Parameter(ValueFromRemainingArguments = $true)]
[String]
$Switches,
[Switch]
$Overwrite,
[Switch]
$Removal
)
$ZstdPath = Get-HelperPath -Helper Zstd
$LogPath = "$(Split-Path $Path)\zstd.log"
$DestinationPath = $DestinationPath.TrimEnd("\")
ensure $DestinationPath | Out-Null
$ArgList = @('-d', "`"$Path`"", '--output-dir-flat', "`"$DestinationPath`"", "-v")
if ($Switches) {
$ArgList += (-split $Switches)
}
if ($Overwrite) {
$ArgList += '-f'
}
$Status = Invoke-ExternalCommand $ZstdPath $ArgList -LogPath $LogPath
if (!$Status) {
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
}
$IsTar = (strip_ext $Path) -match '\.tar$'
if (!$IsTar -and $ExtractDir) {
movedir "$DestinationPath\$ExtractDir" $DestinationPath | Out-Null
}
if (Test-Path $LogPath) {
Remove-Item $LogPath -Force
}
if ($IsTar) {
# Check for tar
$TarFile = (strip_ext $Path)
Expand-7zipArchive -Path "$TarFile" -DestinationPath $DestinationPath -ExtractDir $ExtractDir -Removal
}
if ($Removal) {
# Remove original archive file
Remove-Item $Path -Force
}
}
function Expand-MsiArchive {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[String]
$Path,
[Parameter(Position = 1)]
[String]
$DestinationPath = (Split-Path $Path),
[String]
$ExtractDir,
[Parameter(ValueFromRemainingArguments = $true)]
[String]
$Switches,
[Switch]
$Removal
)
$DestinationPath = $DestinationPath.TrimEnd("\")
if ($ExtractDir) {
$OriDestinationPath = $DestinationPath
$DestinationPath = "$DestinationPath\_tmp"
}
if ((get_config MSIEXTRACT_USE_LESSMSI)) {
$MsiPath = Get-HelperPath -Helper Lessmsi
$ArgList = @('x', "`"$Path`"", "`"$DestinationPath\\`"")
} else {
$MsiPath = 'msiexec.exe'
$ArgList = @('/a', "`"$Path`"", '/qn', "TARGETDIR=`"$DestinationPath\\SourceDir`"")
}
$LogPath = "$(Split-Path $Path)\msi.log"
if ($Switches) {
$ArgList += (-split $Switches)
}
$Status = Invoke-ExternalCommand $MsiPath $ArgList -LogPath $LogPath
if (!$Status) {
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
}
if ($ExtractDir -and (Test-Path "$DestinationPath\SourceDir")) {
movedir "$DestinationPath\SourceDir\$ExtractDir" $OriDestinationPath | Out-Null
Remove-Item $DestinationPath -Recurse -Force
} elseif ($ExtractDir) {
movedir "$DestinationPath\$ExtractDir" $OriDestinationPath | Out-Null
Remove-Item $DestinationPath -Recurse -Force
} elseif (Test-Path "$DestinationPath\SourceDir") {
movedir "$DestinationPath\SourceDir" $DestinationPath | Out-Null
}
if (($DestinationPath -ne (Split-Path $Path)) -and (Test-Path "$DestinationPath\$(fname $Path)")) {
Remove-Item "$DestinationPath\$(fname $Path)" -Force
}
if (Test-Path $LogPath) {
Remove-Item $LogPath -Force
}
if ($Removal) {
# Remove original archive file
Remove-Item $Path -Force
}
}
function Expand-InnoArchive {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[String]
$Path,
[Parameter(Position = 1)]
[String]
$DestinationPath = (Split-Path $Path),
[String]
$ExtractDir,
[Parameter(ValueFromRemainingArguments = $true)]
[String]
$Switches,
[Switch]
$Removal
)
$LogPath = "$(Split-Path $Path)\innounp.log"
$ArgList = @('-x', "-d`"$DestinationPath`"", "`"$Path`"", '-y')
switch -Regex ($ExtractDir) {
"^[^{].*" { $ArgList += "-c{app}\$ExtractDir" }
"^{.*" { $ArgList += "-c$ExtractDir" }
Default { $ArgList += "-c{app}" }
}
if ($Switches) {
$ArgList += (-split $Switches)
}
$Status = Invoke-ExternalCommand (Get-HelperPath -Helper Innounp) $ArgList -LogPath $LogPath
if (!$Status) {
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
}
if (Test-Path $LogPath) {
Remove-Item $LogPath -Force
}
if ($Removal) {
# Remove original archive file
Remove-Item $Path -Force
}
}
function Expand-ZipArchive {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[String]
$Path,
[Parameter(Position = 1)]
[String]
$DestinationPath = (Split-Path $Path),
[String]
$ExtractDir,
[Switch]
$Removal
)
if ($ExtractDir) {
$OriDestinationPath = $DestinationPath
$DestinationPath = "$DestinationPath\_tmp"
}
# All methods to unzip the file require .NET4.5+
if ($PSVersionTable.PSVersion.Major -lt 5) {
Add-Type -AssemblyName System.IO.Compression.FileSystem
try {
[System.IO.Compression.ZipFile]::ExtractToDirectory($Path, $DestinationPath)
} catch [System.IO.PathTooLongException] {
# try to fall back to 7zip if path is too long
if (Test-HelperInstalled -Helper 7zip) {
Expand-7zipArchive $Path $DestinationPath -Removal
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 (Test-HelperInstalled -Helper 7zip) {
Expand-7zipArchive $Path $DestinationPath -Removal
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: $_"
}
} else {
# Use Expand-Archive to unzip in PowerShell 5+
# Compatible with Pscx (https://github.com/Pscx/Pscx)
Microsoft.PowerShell.Archive\Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force
}
if ($ExtractDir) {
movedir "$DestinationPath\$ExtractDir" $OriDestinationPath | Out-Null
Remove-Item $DestinationPath -Recurse -Force
}
if ($Removal) {
# Remove original archive file
Remove-Item $Path -Force
}
}
function Expand-DarkArchive {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[String]
$Path,
[Parameter(Position = 1)]
[String]
$DestinationPath = (Split-Path $Path),
[Parameter(ValueFromRemainingArguments = $true)]
[String]
$Switches,
[Switch]
$Removal
)
$LogPath = "$(Split-Path $Path)\dark.log"
$ArgList = @('-nologo', "-x `"$DestinationPath`"", "`"$Path`"")
if ($Switches) {
$ArgList += (-split $Switches)
}
$Status = Invoke-ExternalCommand (Get-HelperPath -Helper Dark) $ArgList -LogPath $LogPath
if (!$Status) {
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
}
if (Test-Path $LogPath) {
Remove-Item $LogPath -Force
}
if ($Removal) {
# Remove original archive file
Remove-Item $Path -Force
}
}
function extract_7zip($path, $to, $removal) {
Show-DeprecatedWarning $MyInvocation 'Expand-7zipArchive'
Expand-7zipArchive -Path $path -DestinationPath $to -Removal:$removal @args
}
function extract_msi($path, $to, $removal) {
Show-DeprecatedWarning $MyInvocation 'Expand-MsiArchive'
Expand-MsiArchive -Path $path -DestinationPath $to -Removal:$removal
}
function unpack_inno($path, $to, $removal) {
Show-DeprecatedWarning $MyInvocation 'Expand-InnoArchive'
Expand-InnoArchive -Path $path -DestinationPath $to -Removal:$removal @args
}
function extract_zip($path, $to, $removal) {
Show-DeprecatedWarning $MyInvocation 'Expand-ZipArchive'
Expand-ZipArchive -Path $path -DestinationPath $to -Removal:$removal
}
# resolve dependencies for the supplied apps, and sort into the correct order
function install_order($apps, $arch) {
$res = @()
foreach ($app in $apps) {
foreach ($dep in deps $app $arch) {
if ($res -notcontains $dep) { $res += $dep}
}
if ($res -notcontains $app) { $res += $app }
}
return $res
}
# http://www.electricmonk.nl/docs/dependency_resolving_algorithm/dependency_resolving_algorithm.html
function deps($app, $arch) {
$resolved = new-object collections.arraylist
dep_resolve $app $arch $resolved @()
if ($resolved.count -eq 1) { return @() } # no dependencies
return $resolved[0..($resolved.count - 2)]
}
function dep_resolve($app, $arch, $resolved, $unresolved) {
$app, $bucket, $null = parse_app $app
$unresolved += $app
$null, $manifest, $null, $null = Find-Manifest $app $bucket
if(!$manifest) {
if(((Get-LocalBucket) -notcontains $bucket) -and $bucket) {
warn "Bucket '$bucket' not installed. Add it with 'scoop bucket add $bucket' or 'scoop bucket add $bucket <repo>'."
}
abort "Couldn't find manifest for '$app'$(if(!$bucket) { '.' } else { " from '$bucket' bucket." })"
}
$deps = @(install_deps $manifest $arch) + @(runtime_deps $manifest) | Select-Object -Unique
foreach ($dep in $deps) {
if ($resolved -notcontains $dep) {
if ($unresolved -contains $dep) {
abort "Circular dependency detected: '$app' -> '$dep'."
}
dep_resolve $dep $arch $resolved $unresolved
}
}
$resolved.add($app) | Out-Null
$unresolved = $unresolved -ne $app # remove from unresolved
}
function runtime_deps($manifest) {
if ($manifest.depends) { return $manifest.depends }
}
function script_deps($script) {
$deps = @()
if($script -is [Array]) {
$script = $script -join "`n"
}
if([String]::IsNullOrEmpty($script)) {
return $deps
}
if($script -like '*Expand-7zipArchive *' -or $script -like '*extract_7zip *') {
$deps += '7zip'
}
if($script -like '*Expand-MsiArchive *' -or $script -like '*extract_msi *') {
$deps += 'lessmsi'
}
if($script -like '*Expand-InnoArchive *' -or $script -like '*unpack_inno *') {
$deps += 'innounp'
}
if($script -like '*Expand-DarkArchive *') {
$deps += 'dark'
}
if ($script -like '*Expand-ZstdArchive *') {
$deps += 'zstd'
}
return $deps
}
function install_deps($manifest, $arch) {
$deps = @()
if (!(Test-HelperInstalled -Helper 7zip) -and (Test-7zipRequirement -URL (script:url $manifest $arch))) {
$deps += '7zip'
}
if (!(Test-HelperInstalled -Helper Lessmsi) -and (Test-LessmsiRequirement -URL (script:url $manifest $arch))) {
$deps += 'lessmsi'
}
if (!(Test-HelperInstalled -Helper Innounp) -and $manifest.innosetup) {
$deps += 'innounp'
}
if (!(Test-HelperInstalled -Helper Zstd) -and (Test-ZstdRequirement -URL (url $manifest $arch))) {
$deps += 'zstd'
}
$pre_install = arch_specific 'pre_install' $manifest $arch
$installer = arch_specific 'installer' $manifest $arch
$post_install = arch_specific 'post_install' $manifest $arch
$deps += script_deps $pre_install
$deps += script_deps $installer.script
$deps += script_deps $post_install
return $deps | Select-Object -Unique
}
function find_description($url, $html, $redir = $false) {
$meta = meta_tags $html
# check <meta property="og:description">
$og_description = meta_content $meta 'property' 'og:description'
if($og_description) {
return $og_description, '<meta property="og:description">'
}
# check <meta name="description">
$description = meta_content $meta 'name' 'description'
if($description) {
return $description, '<meta name="description">'
}
# check <meta http-equiv="refresh"> redirect
$refresh = meta_refresh $meta $url
if($refresh -and !$redir) {
$wc = New-Object Net.Webclient
$wc.Headers.Add('User-Agent', (Get-UserAgent))
$html = $wc.downloadstring($refresh)
return find_description $refresh $html $true
}
# check text for 'x is ...'
$text = html_text $html $meta
$text_desc = find_is $text
if($text_desc) {
return $text_desc, 'text'
}
# first paragraph
$first_para = first_para $html
if($first_para) {
return $first_para, 'first <p>'
}
return $null, $null
}
function clean_description($description) {
if(!$description) { return $description }
$description = $description -replace '\n', ' '
$description = $description -replace '\s{2,}', ' '
return $description.trim()
}
# Collects meta tags from $html into hashtables.
function meta_tags($html) {
$tags = @()
$meta = ([regex]'<meta [^>]+>').matches($html)
$meta | ForEach-Object {
$attrs = ([regex]'([\w-]+)="([^"]+)"').matches($_.value)
$hash = @{}
$attrs | ForEach-Object {
$hash[$_.groups[1].value] = $_.groups[2].value
}
$tags += $hash
}
$tags
}
function meta_content($tags, $attribute, $search) {
if(!$tags) { return }
return $tags | Where-Object { $_[$attribute] -eq $search } | ForEach-Object { $_['content'] }
}
# Looks for a redirect URL in a <meta> refresh tag.
function meta_refresh($tags, $url) {
$refresh = meta_content $tags 'http-equiv' 'refresh'
if($refresh) {
if($refresh -match '\d+;\s*url\s*=\s*(.*)') {
$refresh_url = $matches[1].trim("'", '"')
if($refresh_url -notmatch '^https?://') {
$refresh_url = "$url$refresh_url"
}
return $refresh_url
}
}
}
function html_body($html) {
if($html -match '(?s)<body[^>]*>(.*?)</body>') {
$body = $matches[1]
$body = $body -replace '(?s)<script[^>]*>.*?</script>', ' '
$body = $body -replace '(?s)<!--.*?-->', ' '
return $body
}
}
function html_text($body, $meta_tags) {
$body = html_body $html
if($body) {
return strip_html $body
}
}
function strip_html($html) {
$html = $html -replace '(?s)<[^>]*>', ' '
$html = $html -replace '\t', ' '
$html = $html -replace '&nbsp;?', ' '
$html = $html -replace '&gt;?', '>'
$html = $html -replace '&lt;?', '<'
$html = $html -replace '&quot;?', '"'
$encoding_meta = meta_content $meta_tags 'http-equiv' 'Content-Type'
if($encoding_meta) {
if($encoding_meta -match 'charset\s*=\s*(.*)') {
$charset = $matches[1]
try {
$encoding = [text.encoding]::getencoding($charset)
} catch {
Write-Warning "Unknown charset"
}
if($encoding) {
$html = ([regex]'&#(\d+);?').replace($html, {
param($m)
try {
return $encoding.getstring($m.Groups[1].Value)
} catch {
return $m.value
}
})
}
}
}
$html = $html -replace '\n +', "`r`n"
$html = $html -replace '\n{2,}', "`r`n"
$html = $html -replace ' {2,}', ' '
$html = $html -replace ' (\.|,)', '$1'
return $html.trim()
}
function find_is($text) {
if($text -match '(?s)[\n\.]((?:[^\n\.])+? is .+?[\.!])') {
return $matches[1].trim()
}
}
function first_para($html) {
$body = html_body $html
if($body -match '(?s)<p[^>]*>(.*?)</p>') {
return strip_html $matches[1]
}
}
<#
Diagnostic tests.
Return $true if the test passed, otherwise $false.
Use 'warn' to highlight the issue, and follow up with the recommended actions to rectify.
#>
. "$PSScriptRoot\buckets.ps1"
function check_windows_defender($global) {
$defender = get-service -name WinDefend -errorAction SilentlyContinue
if($defender -and $defender.status) {
if($defender.status -eq [system.serviceprocess.servicecontrollerstatus]::running) {
if (Test-CommandAvailable Get-MpPreference) {
$installPath = $scoopdir;
if($global) { $installPath = $globaldir; }
$exclusionPath = (Get-MpPreference).exclusionPath
if(!($exclusionPath -contains $installPath)) {
warn "Windows Defender may slow down or disrupt installs with realtime scanning."
write-host " Consider running:"
write-host " sudo Add-MpPreference -ExclusionPath '$installPath'"
write-host " (Requires 'sudo' command. Run 'scoop install sudo' if you don't have it.)"
return $false
}
}
}
}
return $true
}
function check_main_bucket {
if ((Get-LocalBucket) -notcontains 'main'){
warn 'Main bucket is not added.'
Write-Host " run 'scoop bucket add main'"
return $false
}
return $true
}
function check_long_paths {
$key = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -ErrorAction SilentlyContinue -Name 'LongPathsEnabled'
if (!$key -or ($key.LongPathsEnabled -eq 0)) {
warn 'LongPaths support is not enabled.'
Write-Host "You can enable it with running:"
Write-Host " Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1"
return $false
}
return $true
}
function check_envs_requirements {
if ($null -eq $env:COMSPEC) {
warn '$env:COMSPEC environment variable is missing.'
Write-Host " By default the variable should points to the cmd.exe in Windows: '%SystemRoot%\system32\cmd.exe'."
return $false
}
return $true
}
# adapted from http://hg.python.org/cpython/file/2.7/Lib/getopt.py
# argv:
# array of arguments
# shortopts:
# string of single-letter options. options that take a parameter
# should be follow by ':'
# longopts:
# array of strings that are long-form options. options that take
# a parameter should end with '='
# returns @(opts hash, remaining_args array, error string)
function getopt($argv, $shortopts, $longopts) {
$opts = @{}; $rem = @()
function err($msg) {
$opts, $rem, $msg
}
function regex_escape($str) {
return [regex]::escape($str)
}
# ensure these are arrays
$argv = @($argv)
$longopts = @($longopts)
for($i = 0; $i -lt $argv.length; $i++) {
$arg = $argv[$i]
if($null -eq $arg) { continue }
# don't try to parse array arguments
if($arg -is [array]) { $rem += ,$arg; continue }
if($arg -is [int]) { $rem += $arg; continue }
if($arg -is [decimal]) { $rem += $arg; continue }
if($arg.startswith('--')) {
$name = $arg.substring(2)
$longopt = $longopts | Where-Object { $_ -match "^$name=?$" }
if($longopt) {
if($longopt.endswith('=')) { # requires arg
if($i -eq $argv.length - 1) {
return err "Option --$name requires an argument."
}
$opts.$name = $argv[++$i]
} else {
$opts.$name = $true
}
} else {
return err "Option --$name not recognized."
}
} elseif($arg.startswith('-') -and $arg -ne '-') {
for($j = 1; $j -lt $arg.length; $j++) {
$letter = $arg[$j].tostring()
if($shortopts -match "$(regex_escape $letter)`:?") {
$shortopt = $matches[0]
if($shortopt[1] -eq ':') {
if($j -ne $arg.length -1 -or $i -eq $argv.length - 1) {
return err "Option -$letter requires an argument."
}
$opts.$letter = $argv[++$i]
} else {
$opts.$letter = $true
}
} else {
return err "Option -$letter not recognized."
}
}
} else {
$rem += $arg
}
}
$opts, $rem
}
function git_proxy_cmd {
$proxy = get_config 'proxy'
$cmd = "git $($args | ForEach-Object { "$_ " })"
if($proxy -and $proxy -ne 'none') {
$cmd = "SET HTTPS_PROXY=$proxy&&SET HTTP_PROXY=$proxy&&$cmd"
}
& "$env:COMSPEC" /d /c $cmd
}
function git_clone {
git_proxy_cmd clone $args
}
function git_ls_remote {
git_proxy_cmd ls-remote $args
}
function git_checkout {
git_proxy_cmd checkout $args
}
function git_pull {
git_proxy_cmd pull --rebase=false $args
}
function git_fetch {
git_proxy_cmd fetch $args
}
function git_checkout {
git_proxy_cmd checkout $args
}
function usage($text) {
$text | Select-String '(?m)^# Usage: ([^\n]*)$' | ForEach-Object { "Usage: " + $_.matches[0].groups[1].value }
}
function summary($text) {
$text | Select-String '(?m)^# Summary: ([^\n]*)$' | ForEach-Object { $_.matches[0].groups[1].value }
}
function scoop_help($text) {
$help_lines = $text | Select-String '(?ms)^# Help:(.(?!^[^#]))*' | ForEach-Object { $_.matches[0].value; }
$help_lines -replace '(?ms)^#\s?(Help: )?', ''
}
function my_usage { # gets usage for the calling script
usage (Get-Content $myInvocation.PSCommandPath -raw)
}
此差异已折叠。
# Convert objects to pretty json
# Only needed until PowerShell ConvertTo-Json will be improved https://github.com/PowerShell/PowerShell/issues/2736
# https://github.com/PowerShell/PowerShell/issues/2736 was fixed in pwsh
# Still needed in normal powershell
function ConvertToPrettyJson {
[CmdletBinding()]
Param (
[Parameter(Mandatory, ValueFromPipeline)]
$data
)
Process {
$data = normalize_values $data
# convert to string
[String]$json = $data | ConvertTo-Json -Depth 8 -Compress
[String]$output = ''
# state
[String]$buffer = ''
[Int]$depth = 0
[Bool]$inString = $false
# configuration
[String]$indent = ' ' * 4
[Bool]$unescapeString = $true
[String]$eol = "`r`n"
for ($i = 0; $i -lt $json.Length; $i++) {
# read current char
$buffer = $json.Substring($i, 1)
$objectStart = !$inString -and $buffer.Equals('{')
$objectEnd = !$inString -and $buffer.Equals('}')
$arrayStart = !$inString -and $buffer.Equals('[')
$arrayEnd = !$inString -and $buffer.Equals(']')
$colon = !$inString -and $buffer.Equals(':')
$comma = !$inString -and $buffer.Equals(',')
$quote = $buffer.Equals('"')
$escape = $buffer.Equals('\')
if ($quote) {
$inString = !$inString
}
# skip escape sequences
if ($escape) {
$buffer = $json.Substring($i, 2)
++$i
# Unescape unicode
if ($inString -and $unescapeString) {
if ($buffer.Equals('\n')) {
$buffer = "`n"
} elseif ($buffer.Equals('\r')) {
$buffer = "`r"
} elseif ($buffer.Equals('\t')) {
$buffer = "`t"
} elseif ($buffer.Equals('\u')) {
$buffer = [regex]::Unescape($json.Substring($i - 1, 6))
$i += 4
}
}
$output += $buffer
continue
}
# indent / outdent
if ($objectStart -or $arrayStart) {
++$depth
} elseif ($objectEnd -or $arrayEnd) {
--$depth
$output += $eol + ($indent * $depth)
}
# add content
$output += $buffer
# add whitespace and newlines after the content
if ($colon) {
$output += ' '
} elseif ($comma -or $arrayStart -or $objectStart) {
$output += $eol
$output += $indent * $depth
}
}
return $output
}
}
function json_path([String] $json, [String] $jsonpath, [Hashtable] $substitutions) {
Add-Type -Path "$psscriptroot\..\supporting\validator\bin\Newtonsoft.Json.dll"
if ($null -ne $substitutions) {
$jsonpath = substitute $jsonpath $substitutions ($jsonpath -like "*=~*")
}
try {
$obj = [Newtonsoft.Json.Linq.JObject]::Parse($json)
} catch [Newtonsoft.Json.JsonReaderException] {
try {
$obj = [Newtonsoft.Json.Linq.JArray]::Parse($json)
} catch [Newtonsoft.Json.JsonReaderException] {
return $null
}
}
try {
try {
$result = $obj.SelectToken($jsonpath, $true)
} catch [Newtonsoft.Json.JsonException] {
return $null
}
return $result.ToString()
} catch [System.Management.Automation.MethodInvocationException] {
write-host -f DarkRed $_
return $null
}
return $null
}
function json_path_legacy([String] $json, [String] $jsonpath, [Hashtable] $substitutions) {
$result = $json | ConvertFrom-Json -ea stop
$isJsonPath = $jsonpath.StartsWith('$')
$jsonpath.split('.') | ForEach-Object {
$el = $_
# substitute the basename and version varibales into the jsonpath
if ($null -ne $substitutions) {
$el = substitute $el $substitutions
}
# skip $ if it's jsonpath format
if ($el -eq '$' -and $isJsonPath) {
return
}
# array detection
if ($el -match '^(?<property>\w+)?\[(?<index>\d+)\]$') {
$property = $matches['property']
if ($property) {
$result = $result.$property[$matches['index']]
} else {
$result = $result[$matches['index']]
}
return
}
$result = $result.$el
}
return $result
}
function normalize_values([psobject] $json) {
# Iterate Through Manifest Properties
$json.PSObject.Properties | ForEach-Object {
# Recursively edit psobjects
# If the values is psobjects, its not normalized
# For example if manifest have architecture and it's architecture have array with single value it's not formatted.
# @see https://github.com/lukesampson/scoop/pull/2642#issue-220506263
if ($_.Value -is [System.Management.Automation.PSCustomObject]) {
$_.Value = normalize_values $_.Value
}
# Process String Values
if ($_.Value -is [String]) {
# Split on new lines
[Array] $parts = ($_.Value -split '\r?\n').Trim()
# Replace with string array if result is multiple lines
if ($parts.Count -gt 1) {
$_.Value = $parts
}
}
# Convert single value array into string
if ($_.Value -is [Array]) {
# Array contains only 1 element String or Array
if ($_.Value.Count -eq 1) {
# Array
if ($_.Value[0] -is [Array]) {
$_.Value = $_.Value
} else {
# String
$_.Value = $_.Value[0]
}
} else {
# Array of Arrays
$resulted_arrs = @()
foreach ($element in $_.Value) {
if ($element.Count -eq 1) {
$resulted_arrs += $element
} else {
$resulted_arrs += , $element
}
}
$_.Value = $resulted_arrs
}
}
# Process other values as needed...
}
return $json
}
. "$psscriptroot/core.ps1"
. "$psscriptroot/autoupdate.ps1"
function manifest_path($app, $bucket) {
fullpath "$(Find-BucketDirectory $bucket)\$(sanitary_path $app).json"
}
function parse_json($path) {
if(!(test-path $path)) { return $null }
Get-Content $path -raw -Encoding UTF8 | convertfrom-json -ea stop
}
function url_manifest($url) {
$str = $null
try {
$wc = New-Object Net.Webclient
$wc.Headers.Add('User-Agent', (Get-UserAgent))
$str = $wc.downloadstring($url)
} catch [system.management.automation.methodinvocationexception] {
warn "error: $($_.exception.innerexception.message)"
} catch {
throw
}
if(!$str) { return $null }
$str | convertfrom-json
}
function manifest($app, $bucket, $url) {
if($url) { return url_manifest $url }
parse_json (manifest_path $app $bucket)
}
function save_installed_manifest($app, $bucket, $dir, $url) {
if($url) {
$wc = New-Object Net.Webclient
$wc.Headers.Add('User-Agent', (Get-UserAgent))
$wc.downloadstring($url) > "$dir\manifest.json"
} else {
Copy-Item (manifest_path $app $bucket) "$dir\manifest.json"
}
}
function installed_manifest($app, $version, $global) {
parse_json "$(versiondir $app $version $global)\manifest.json"
}
function save_install_info($info, $dir) {
$nulls = $info.keys | Where-Object { $null -eq $info[$_] }
$nulls | ForEach-Object { $info.remove($_) } # strip null-valued
$file_content = $info | ConvertToPrettyJson
[System.IO.File]::WriteAllLines("$dir\install.json", $file_content)
}
function install_info($app, $version, $global) {
$path = "$(versiondir $app $version $global)\install.json"
if(!(test-path $path)) { return $null }
parse_json $path
}
function default_architecture {
$arch = get_config 'default-architecture'
$system = if ([Environment]::Is64BitOperatingSystem) { '64bit' } else { '32bit' }
if ($null -eq $arch) {
$arch = $system
} else {
try {
$arch = ensure_architecture $arch
} catch {
warn 'Invalid default architecture configured. Determining default system architecture'
$arch = $system
}
}
return $arch
}
function arch_specific($prop, $manifest, $architecture) {
if($manifest.architecture) {
$val = $manifest.architecture.$architecture.$prop
if($val) { return $val } # else fallback to generic prop
}
if($manifest.$prop) { return $manifest.$prop }
}
function supports_architecture($manifest, $architecture) {
return -not [String]::IsNullOrEmpty((arch_specific 'url' $manifest $architecture))
}
function generate_user_manifest($app, $bucket, $version) {
$null, $manifest, $bucket, $null = Find-Manifest $app $bucket
if ("$($manifest.version)" -eq "$version") {
return manifest_path $app $bucket
}
warn "Given version ($version) does not match manifest ($($manifest.version))"
warn "Attempting to generate manifest for '$app' ($version)"
if (!($manifest.autoupdate)) {
abort "'$app' does not have autoupdate capability`r`ncouldn't find manifest for '$app@$version'"
}
ensure $(usermanifestsdir) | out-null
try {
Invoke-AutoUpdate $app "$(resolve-path $(usermanifestsdir))" $manifest $version $(@{ })
return "$(resolve-path $(usermanifest $app))"
} catch {
write-host -f darkred "Could not install $app@$version"
}
return $null
}
function url($manifest, $arch) { arch_specific 'url' $manifest $arch }
function installer($manifest, $arch) { arch_specific 'installer' $manifest $arch }
function uninstaller($manifest, $arch) { arch_specific 'uninstaller' $manifest $arch }
function msi($manifest, $arch) { arch_specific 'msi' $manifest $arch }
function hash($manifest, $arch) { arch_specific 'hash' $manifest $arch }
function extract_dir($manifest, $arch) { arch_specific 'extract_dir' $manifest $arch}
function extract_to($manifest, $arch) { arch_specific 'extract_to' $manifest $arch}
$modulesdir = "$scoopdir\modules"
function install_psmodule($manifest, $dir, $global) {
$psmodule = $manifest.psmodule
if(!$psmodule) { return }
if($global) {
abort "Installing PowerShell modules globally is not implemented!"
}
$modulesdir = ensure $modulesdir
ensure_in_psmodulepath $modulesdir $global
$module_name = $psmodule.name
if(!$module_name) {
abort "Invalid manifest: The 'name' property is missing from 'psmodule'."
}
$linkfrom = "$modulesdir\$module_name"
write-host "Installing PowerShell module '$module_name'"
write-host "Linking $(friendly_path $linkfrom) => $(friendly_path $dir)"
if(test-path $linkfrom) {
warn "$(friendly_path $linkfrom) already exists. It will be replaced."
& "$env:COMSPEC" /c "rmdir `"$linkfrom`""
}
& "$env:COMSPEC" /c "mklink /j `"$linkfrom`" `"$dir`"" | out-null
}
function uninstall_psmodule($manifest, $dir, $global) {
$psmodule = $manifest.psmodule
if(!$psmodule) { return }
$module_name = $psmodule.name
write-host "Uninstalling PowerShell module '$module_name'."
$linkfrom = "$modulesdir\$module_name"
if(test-path $linkfrom) {
write-host "Removing $(friendly_path $linkfrom)"
$linkfrom = resolve-path $linkfrom
& "$env:COMSPEC" /c "rmdir `"$linkfrom`""
}
}
function ensure_in_psmodulepath($dir, $global) {
$path = env 'psmodulepath' $global
if(!$global -and $null -eq $path) {
$path = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules"
}
$dir = fullpath $dir
if($path -notmatch [regex]::escape($dir)) {
write-output "Adding $(friendly_path $dir) to $(if($global){'global'}else{'your'}) PowerShell module path."
env 'psmodulepath' $global "$dir;$path" # for future sessions...
$env:psmodulepath = "$dir;$env:psmodulepath" # for this session
}
}
# Creates shortcut for the app in the start menu
function create_startmenu_shortcuts($manifest, $dir, $global, $arch) {
$shortcuts = @(arch_specific 'shortcuts' $manifest $arch)
$shortcuts | Where-Object { $_ -ne $null } | ForEach-Object {
$target = [System.IO.Path]::Combine($dir, $_.item(0))
$target = New-Object System.IO.FileInfo($target)
$name = $_.item(1)
$arguments = ""
$icon = $null
if($_.length -ge 3) {
$arguments = $_.item(2)
}
if($_.length -ge 4) {
$icon = [System.IO.Path]::Combine($dir, $_.item(3))
$icon = New-Object System.IO.FileInfo($icon)
}
$arguments = (substitute $arguments @{ '$dir' = $dir; '$original_dir' = $original_dir; '$persist_dir' = $persist_dir})
startmenu_shortcut $target $name $arguments $icon $global
}
}
function shortcut_folder($global) {
$directory = [System.IO.Path]::Combine([Environment]::GetFolderPath('startmenu'), 'Programs', 'Scoop Apps')
if($global) {
$directory = [System.IO.Path]::Combine([Environment]::GetFolderPath('commonstartmenu'), 'Programs', 'Scoop Apps')
}
return $(ensure $directory)
}
function startmenu_shortcut([System.IO.FileInfo] $target, $shortcutName, $arguments, [System.IO.FileInfo]$icon, $global) {
if(!$target.Exists) {
Write-Host -f DarkRed "Creating shortcut for $shortcutName ($(fname $target)) failed: Couldn't find $target"
return
}
if($icon -and !$icon.Exists) {
Write-Host -f DarkRed "Creating shortcut for $shortcutName ($(fname $target)) failed: Couldn't find icon $icon"
return
}
$scoop_startmenu_folder = shortcut_folder $global
$subdirectory = [System.IO.Path]::GetDirectoryName($shortcutName)
if ($subdirectory) {
$subdirectory = ensure $([System.IO.Path]::Combine($scoop_startmenu_folder, $subdirectory))
}
$wsShell = New-Object -ComObject WScript.Shell
$wsShell = $wsShell.CreateShortcut("$scoop_startmenu_folder\$shortcutName.lnk")
$wsShell.TargetPath = $target.FullName
$wsShell.WorkingDirectory = $target.DirectoryName
if ($arguments) {
$wsShell.Arguments = $arguments
}
if($icon -and $icon.Exists) {
$wsShell.IconLocation = $icon.FullName
}
$wsShell.Save()
write-host "Creating shortcut for $shortcutName ($(fname $target))"
}
# Removes the Startmenu shortcut if it exists
function rm_startmenu_shortcuts($manifest, $global, $arch) {
$shortcuts = @(arch_specific 'shortcuts' $manifest $arch)
$shortcuts | Where-Object { $_ -ne $null } | ForEach-Object {
$name = $_.item(1)
$shortcut = "$(shortcut_folder $global)\$name.lnk"
write-host "Removing shortcut $(friendly_path $shortcut)"
if(Test-Path -Path $shortcut) {
Remove-Item $shortcut
}
# Before issue 1514 Startmenu shortcut removal
#
# Shortcuts that should have been installed globally would
# have been installed locally up until 27 June 2017.
#
# TODO: Remove this 'if' block and comment after
# 27 June 2018.
if($global) {
$shortcut = "$(shortcut_folder $false)\$name.lnk"
if(Test-Path -Path $shortcut) {
Remove-Item $shortcut
}
}
}
}
# Note: This file is for overwriting global variables and functions to make
# them unix compatible. It has to be imported after everything else!
function is_unix() { $PSVersionTable.Platform -eq 'Unix' }
function is_mac() { $PSVersionTable.OS.ToLower().StartsWith('darwin') }
function is_linux() { $PSVersionTable.OS.ToLower().StartsWith('linux') }
if(!(is_unix)) {
return # get the hell outta here
}
# core.ps1
$scoopdir = $env:SCOOP, (get_config 'rootPath'), (Join-Path $env:HOME "scoop") | Select-Object -first 1
$globaldir = $env:SCOOP_GLOBAL, (get_config 'globalPath'), "/usr/local/scoop" | Select-Object -first 1
$cachedir = $env:SCOOP_CACHE, (get_config 'cachePath'), (Join-Path $scoopdir "cache") | Select-Object -first 1
# core.ps1
function ensure($dir) {
mkdir -p $dir > $null
return resolve-path $dir
}
# install.ps1
function compute_hash($file, $algname) {
if(is_mac) {
switch ($algname)
{
"md5" { $result = (md5 -q $file) }
"sha1" { $result = (shasum -ba 1 $file) }
"sha256" { $result = (shasum -ba 256 $file) }
"sha512" { $result = (shasum -ba 512 $file) }
default { $result = (shasum -ba 256 $file) }
}
} else {
switch ($algname)
{
"md5" { $result = (md5sum -b $file) }
"sha1" { $result = (sha1sum -b $file) }
"sha256" { $result = (sha256sum -b $file) }
"sha512" { $result = (sha512sum -b $file) }
default { $result = (sha256sum -b $file) }
}
}
return $result.split(' ') | Select-Object -first 1
}
# versions
function Get-LatestVersion {
<#
.SYNOPSIS
Get latest version of app from manifest
.PARAMETER AppName
App's name
.PARAMETER Bucket
Bucket which the app belongs to
.PARAMETER Uri
Remote app manifest's URI
#>
[OutputType([String])]
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[Alias('App')]
[String]
$AppName,
[Parameter(Position = 1)]
[String]
$Bucket,
[Parameter(Position = 2)]
[String]
$Uri
)
process {
return (manifest $AppName $Bucket $Uri).version
}
}
function Select-CurrentVersion {
<#
.SYNOPSIS
Select current version of installed app, from 'current\manifest.json' or modified time of version directory
.PARAMETER AppName
App's name
.PARAMETER Global
Globally installed application
#>
[OutputType([String])]
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[Alias('App')]
[String]
$AppName,
[Parameter(Position = 1)]
[Switch]
$Global
)
process {
$appPath = appdir $AppName $Global
if (Test-Path "$appPath\current" -PathType Container) {
$currentVersion = (installed_manifest $AppName 'current' $Global).version
if ($currentVersion -eq 'nightly') {
$currentVersion = (Get-Item "$appPath\current").Target | Split-Path -Leaf
}
} else {
$installedVersion = Get-InstalledVersion -AppName $AppName -Global:$Global
if ($installedVersion) {
$currentVersion = $installedVersion[-1]
} else {
$currentVersion = $null
}
}
return $currentVersion
}
}
function Get-InstalledVersion {
<#
.SYNOPSIS
Get all installed version of app, by checking version directories' 'install.json'
.PARAMETER AppName
App's name
.PARAMETER Global
Globally installed application
.NOTES
Versions are sorted from oldest to newest, i.e., latest installed version is the last one in the output array.
If no installed version found, empty array will be returned.
#>
[OutputType([Object[]])]
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[Alias('App')]
[String]
$AppName,
[Parameter(Position = 1)]
[Switch]
$Global
)
process {
$appPath = appdir $AppName $Global
if (Test-Path $appPath) {
$versions = @((Get-ChildItem "$appPath\*\install.json" | Sort-Object -Property LastWriteTimeUtc).Directory.Name)
return $versions | Where-Object { ($_ -ne 'current') -and ($_ -notlike '_*.old*') }
} else {
return @()
}
}
# Deprecated
# sort_versions (Get-ChildItem $appPath -dir -attr !reparsePoint | Where-Object { $null -ne $(Get-ChildItem $_.FullName) } | ForEach-Object { $_.Name })
}
function Compare-Version {
<#
.SYNOPSIS
Compare versions, mainly according to SemVer's rules
.PARAMETER ReferenceVersion
Specifies a version used as a reference for comparison
.PARAMETER DifferenceVersion
Specifies the version that are compared to the reference version
.PARAMETER Delimiter
Specifies the delimiter of versions
.OUTPUTS
System.Int32
'0' if DifferenceVersion is equal to ReferenceVersion,
'1' if DifferenceVersion is greater then ReferenceVersion,
'-1' if DifferenceVersion is less then ReferenceVersion
#>
[OutputType([Int32])]
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0)]
[AllowEmptyString()]
[String]
$ReferenceVersion,
[Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)]
[AllowEmptyString()]
[String]
$DifferenceVersion,
[String]
$Delimiter = '-'
)
process {
# Use '+' sign as post-release, see https://github.com/lukesampson/scoop/pull/3721#issuecomment-553718093
$ReferenceVersion, $DifferenceVersion = @($ReferenceVersion, $DifferenceVersion) -replace '\+', '-'
# Return 0 if versions are equal
if ($DifferenceVersion -eq $ReferenceVersion) {
return 0
}
# Preprocess versions (split, convert and separate)
$splitReferenceVersion = @(SplitVersion -Version $ReferenceVersion -Delimiter $Delimiter)
$splitDifferenceVersion = @(SplitVersion -Version $DifferenceVersion -Delimiter $Delimiter)
# Nightly versions are always equal
if ($splitReferenceVersion[0] -eq 'nightly' -and $splitDifferenceVersion[0] -eq 'nightly') {
return 0
}
for ($i = 0; $i -lt [Math]::Max($splitReferenceVersion.Length, $splitDifferenceVersion.Length); $i++) {
# '1.1-alpha' is less then '1.1'
if ($i -ge $splitReferenceVersion.Length) {
if ($splitDifferenceVersion[$i] -match 'alpha|beta|rc|pre') {
return -1
} else {
return 1
}
}
# '1.1' is greater then '1.1-beta'
if ($i -ge $splitDifferenceVersion.Length) {
if ($splitReferenceVersion[$i] -match 'alpha|beta|rc|pre') {
return 1
} else {
return -1
}
}
# If some parts of versions have '.', compare them with delimiter '.'
if (($splitReferenceVersion[$i] -match '\.') -or ($splitDifferenceVersion[$i] -match '\.')) {
$Result = Compare-Version -ReferenceVersion $splitReferenceVersion[$i] -DifferenceVersion $splitDifferenceVersion[$i] -Delimiter '.'
# If the parts are equal, continue to next part, otherwise return
if ($Result -ne 0) {
return $Result
} else {
continue
}
}
# If some parts of versions have '_', compare them with delimiter '_'
if (($splitReferenceVersion[$i] -match '_') -or ($splitDifferenceVersion[$i] -match '_')) {
$Result = Compare-Version -ReferenceVersion $splitReferenceVersion[$i] -DifferenceVersion $splitDifferenceVersion[$i] -Delimiter '_'
# If the parts are equal, continue to next part, otherwise return
if ($Result -ne 0) {
return $Result
} else {
continue
}
}
# Don't try to compare [Long] to [String]
if ($null -ne $splitReferenceVersion[$i] -and $null -ne $splitDifferenceVersion[$i]) {
if ($splitReferenceVersion[$i] -is [String] -and $splitDifferenceVersion[$i] -isnot [String]) {
$splitDifferenceVersion[$i] = "$($splitDifferenceVersion[$i])"
}
if ($splitDifferenceVersion[$i] -is [String] -and $splitReferenceVersion[$i] -isnot [String]) {
$splitReferenceVersion[$i] = "$($splitReferenceVersion[$i])"
}
}
# Compare [String] or [Long]
if ($splitDifferenceVersion[$i] -gt $splitReferenceVersion[$i]) {
return 1
}
if ($splitDifferenceVersion[$i] -lt $splitReferenceVersion[$i]) {
return -1
}
}
}
}
# Helper function
function SplitVersion {
<#
.SYNOPSIS
Split version by Delimiter, convert number string to number, and separate letters from numbers
.PARAMETER Version
Specifies a version
.PARAMETER Delimiter
Specifies the delimiter of version (Literal)
#>
[OutputType([Object[]])]
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[AllowEmptyString()]
[String]
$Version,
[String]
$Delimiter = '-'
)
process {
$Version = $Version -replace '[a-zA-Z]+', "$Delimiter$&$Delimiter"
return ($Version -split [Regex]::Escape($Delimiter) -ne '' | ForEach-Object { if ($_ -match '^\d+$') { [Long]$_ } else { $_ } })
}
}
# Deprecated
# Not used anymore in scoop core
function qsort($ary, $fn) {
warn '"qsort" is deprecated. Please avoid using it anymore.'
if ($null -eq $ary) { return @() }
if (!($ary -is [array])) { return @($ary) }
$pivot = $ary[0]
$rem = $ary[1..($ary.length - 1)]
$lesser = qsort ($rem | Where-Object { (& $fn $pivot $_) -lt 0 }) $fn
$greater = qsort ($rem | Where-Object { (& $fn $pivot $_) -ge 0 }) $fn
return @() + $lesser + @($pivot) + $greater
}
# Deprecated
# Not used anymore in scoop core
function sort_versions($versions) {
warn '"sort_versions" is deprecated. Please avoid using it anymore.'
qsort $versions Compare-Version
}
function compare_versions($a, $b) {
Show-DeprecatedWarning $MyInvocation 'Compare-Version'
# Please note the parameters' sequence
return Compare-Version -ReferenceVersion $b -DifferenceVersion $a
}
function latest_version($app, $bucket, $url) {
Show-DeprecatedWarning $MyInvocation 'Get-LatestVersion'
return Get-LatestVersion -AppName $app -Bucket $bucket -Uri $url
}
function current_version($app, $global) {
Show-DeprecatedWarning $MyInvocation 'Select-CurrentVersion'
return Select-CurrentVersion -AppName $app -Global:$global
}
function versions($app, $global) {
Show-DeprecatedWarning $MyInvocation 'Get-InstalledVersion'
return Get-InstalledVersion -AppName $app -Global:$global
}
此差异已折叠。
# Usage: scoop bucket add|list|known|rm [<args>]
# Summary: Manage Scoop buckets
# Help: Add, list or remove buckets.
#
# Buckets are repositories of apps available to install. Scoop comes with
# a default bucket, but you can also add buckets that you or others have
# published.
#
# To add a bucket:
# scoop bucket add <name> [<repo>]
#
# e.g.:
# scoop bucket add extras https://github.com/lukesampson/scoop-extras.git
#
# Since the 'extras' bucket is known to Scoop, this can be shortened to:
# scoop bucket add extras
#
# To list all known buckets, use:
# scoop bucket known
param($cmd, $name, $repo)
. "$psscriptroot\..\lib\core.ps1"
. "$psscriptroot\..\lib\buckets.ps1"
. "$psscriptroot\..\lib\help.ps1"
. "$psscriptroot\..\lib\git.ps1"
reset_aliases
$usage_add = "usage: scoop bucket add <name> [<repo>]"
$usage_rm = "usage: scoop bucket rm <name>"
switch($cmd) {
'add' { add_bucket $name $repo }
'rm' { rm_bucket $name }
'list' { Get-LocalBucket }
'known' { known_buckets }
default { "scoop bucket: cmd '$cmd' not supported"; my_usage; exit 1 }
}
exit 0
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
# Usage: scoop depends <app>
# Summary: List dependencies for an app
. "$psscriptroot\..\lib\depends.ps1"
. "$psscriptroot\..\lib\install.ps1"
. "$psscriptroot\..\lib\manifest.ps1"
. "$psscriptroot\..\lib\buckets.ps1"
. "$psscriptroot\..\lib\getopt.ps1"
. "$psscriptroot\..\lib\decompress.ps1"
. "$psscriptroot\..\lib\help.ps1"
reset_aliases
$opt, $apps, $err = getopt $args 'a:' 'arch='
$app = $apps[0]
if(!$app) { error '<app> missing'; my_usage; exit 1 }
$architecture = default_architecture
try {
$architecture = ensure_architecture ($opt.a + $opt.arch)
} catch {
abort "ERROR: $_"
}
$deps = @(deps $app $architecture)
if($deps) {
$deps[($deps.length - 1)..0]
}
exit 0
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
9726c3a429009a5b22bd92cb8ab96724c670e164e7240e83f27b7c8b7bd1ca39 *shim.exe
18a737674afde4d5e7e1647d8d1e98471bb260513c57739651f92fdf1647d76c92f0cd0a9bb458daf4eae4bdab9d31404162acf6d74a041e6415752b75d722e0 *shim.exe
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
70d4690b8ac3b3f715f537cdea6e07a39fda4bc0347bf6b958e4f3ff2f0e04d4 shim.exe
ecde07b32192846c4885cf4d2208eedc170765ea115ae49b81509fed0ce474e21064100bb2f3d815ee79f1c12463d32ef013d4182647eae71855cd18e4196176 shim.exe
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册