3 Commits

Author SHA1 Message Date
Ross Smith II
2745b0f42b sandbox.ps1: Add --no-update-scoop install option 2023-01-24 16:43:59 -08:00
Ross Smith II
212dc3d2ee Fix URL in changelog 2023-01-20 08:37:22 -08:00
Ross Smith II
39da19bcfc Add bin/sandbox.ps1 2023-01-19 22:16:00 -08:00
93 changed files with 2491 additions and 3181 deletions

View File

@@ -1,8 +0,0 @@
---
# ~/.github/dependabot.yml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/" # == /.github/workflows/
schedule:
interval: "daily"

View File

@@ -14,7 +14,7 @@ jobs:
with: with:
fetch-depth: 2 fetch-depth: 2
- name: Init Test Suite - name: Init Test Suite
uses: potatoqualitee/psmodulecache@main uses: potatoqualitee/psmodulecache@v5.1
with: with:
modules-to-cache: BuildHelpers modules-to-cache: BuildHelpers
shell: powershell shell: powershell
@@ -30,7 +30,7 @@ jobs:
with: with:
fetch-depth: 2 fetch-depth: 2
- name: Init Test Suite - name: Init Test Suite
uses: potatoqualitee/psmodulecache@main uses: potatoqualitee/psmodulecache@v5.1
with: with:
modules-to-cache: BuildHelpers modules-to-cache: BuildHelpers
shell: pwsh shell: pwsh

1
.gitignore vendored
View File

@@ -6,4 +6,3 @@ test/installer/tmp/*
test/tmp/* test/tmp/*
*~ *~
TestResults.xml TestResults.xml
supporting/sqlite/*

View File

@@ -1,187 +1,25 @@
## [v0.5.3](https://github.com/ScoopInstaller/Scoop/compare/v0.5.2...v0.5.3) - 2025-08-11 ## [Unreleased](https://github.com/ScoopInstaller/Scoop/compare/master...develop)
### Features
**autoupdate:** GitHub predefined hashes support ([#6416](https://github.com/ScoopInstaller/Scoop/issues/6416), [#6435](https://github.com/ScoopInstaller/Scoop/issues/6435))
### Bug Fixes
- **scoop-download|install|update:** Fallback to default downloader when aria2 fails ([#4292](https://github.com/ScoopInstaller/Scoop/issues/4292))
- **decompress**: `Expand-7zipArchive` only delete temp dir / `$extractDir` if it is empty ([#6092](https://github.com/ScoopInstaller/Scoop/issues/6092))
- **decompress**: Replace deprecated 7ZIPEXTRACT_USE_EXTERNAL config with USE_EXTERNAL_7ZIP ([#6327](https://github.com/ScoopInstaller/Scoop/issues/6327))
- **commands**: Handling broken aliases ([#6141](https://github.com/ScoopInstaller/Scoop/issues/6141))
- **shim:** Do not suppress `stderr`, properly check `wslpath`/`cygpath` command first ([#6114](https://github.com/ScoopInstaller/Scoop/issues/6114))
- **scoop-bucket:** Add missing import for `no_junction` envs ([#6181](https://github.com/ScoopInstaller/Scoop/issues/6181))
- **scoop-uninstall:** Fix uninstaller does not gain Global state ([#6430](https://github.com/ScoopInstaller/Scoop/issues/6430))
- **scoop-depends-tests:** Mocking `USE_EXTERNAL_7ZIP` as $false to avoding error when it is $true ([#6431](https://github.com/ScoopInstaller/Scoop/issues/6431))
### Code Refactoring
- **download:** Move download-related functions to 'download.ps1' ([#6095](https://github.com/ScoopInstaller/Scoop/issues/6095))
- **Get-Manifest:** Select actual source for manifest ([#6142](https://github.com/ScoopInstaller/Scoop/issues/6142))
### Performance Improvements
- **shim:** Update kiennq-shim to v3.1.2 ([#6261](https://github.com/ScoopInstaller/Scoop/issues/6261))
## [v0.5.2](https://github.com/ScoopInstaller/Scoop/compare/v0.5.1...v0.5.2) - 2024-07-26
### Bug Fixes
- **scoop-alias:** Fix 'Option --verbose not recognized.' ([#6062](https://github.com/ScoopInstaller/Scoop/issues/6062))
- **scoop-hold:** Use 'foreach' loop to allow 'continue' statement ([#6078](https://github.com/ScoopInstaller/Scoop/issues/6078))
- **core:** Use 'Join-Path' to construct cache file path ([#6079](https://github.com/ScoopInstaller/Scoop/issues/6079))
- **json:** Don't serialize jsonpath return if only one result ([#6066](https://github.com/ScoopInstaller/Scoop/issues/6066), [#6073](https://github.com/ScoopInstaller/Scoop/issues/6073))
### Builds
- **supporting:** Update Json.Schema to 4.0.1 ([#6072](https://github.com/ScoopInstaller/Scoop/issues/6072))
## [v0.5.1](https://github.com/ScoopInstaller/Scoop/compare/v0.5.0...v0.5.1) - 2024-07-16
### Bug Fixes
- **scoop-alias:** Pass options correctly ([#6003](https://github.com/ScoopInstaller/Scoop/issues/6003))
- **scoop-virustotal:** Adjust `json_path` parameters to retrieve correct analysis stats ([#6044](https://github.com/ScoopInstaller/Scoop/issues/6044))
- **bucket:** Implement error handling for failed bucket addition ([#6051](https://github.com/ScoopInstaller/Scoop/issues/6051))
- **database:** Fix compatibility with Windows PowerShell ([#6045](https://github.com/ScoopInstaller/Scoop/issues/6045))
- **install:** Expand `env_set` items before setting Environment Variables ([#6050](https://github.com/ScoopInstaller/Scoop/issues/6050))
- **install:** Fix parsing error when installing multiple apps w/ specific version ([#6039](https://github.com/ScoopInstaller/Scoop/issues/6039))
## [v0.5.0](https://github.com/ScoopInstaller/Scoop/compare/v0.4.2...v0.5.0) - 2024-07-01
### Features
- **scoop-search:** Use SQLite for caching apps to speed up local search ([#5851](https://github.com/ScoopInstaller/Scoop/issues/5851), [#5918](https://github.com/ScoopInstaller/Scoop/issues/5918), [#5946](https://github.com/ScoopInstaller/Scoop/issues/5946), [#5949](https://github.com/ScoopInstaller/Scoop/issues/5949), [#5955](https://github.com/ScoopInstaller/Scoop/issues/5955), [#5966](https://github.com/ScoopInstaller/Scoop/issues/5966), [#5967](https://github.com/ScoopInstaller/Scoop/issues/5967), [#5981](https://github.com/ScoopInstaller/Scoop/issues/5981))
- **core:** New cache filename format ([#5929](https://github.com/ScoopInstaller/Scoop/issues/5929), [#5944](https://github.com/ScoopInstaller/Scoop/issues/5944))
- **decompress:** Use innounp-unicode as default Inno Setup Unpacker ([#6028](https://github.com/ScoopInstaller/Scoop/issues/6028))
- **install:** Added the ability to install specific version of app from URL/file link ([#5988](https://github.com/ScoopInstaller/Scoop/issues/5988))
### Bug Fixes
- **scoop-download|install|update:** Use consistent options ([#5956](https://github.com/ScoopInstaller/Scoop/issues/5956))
- **scoop-info:** Fix download size estimating ([#5958](https://github.com/ScoopInstaller/Scoop/issues/5958))
- **scoop-search:** Catch error of parsing invalid manifest ([#5930](https://github.com/ScoopInstaller/Scoop/issues/5930))
- **checkver:** Correct variable 'regex' to 'regexp' ([#5993](https://github.com/ScoopInstaller/Scoop/issues/5993))
- **checkver:** Correct error messages ([#6024](https://github.com/ScoopInstaller/Scoop/issues/6024))
- **core:** Search for Git executable instead of any cmdlet ([#5998](https://github.com/ScoopInstaller/Scoop/issues/5998))
- **core:** Use correct path in 'bash' ([#6006](https://github.com/ScoopInstaller/Scoop/issues/6006))
- **core:** Limit the number of commands to get when search for git executable ([#6013](https://github.com/ScoopInstaller/Scoop/issues/6013))
- **decompress:** Match `extract_dir`/`extract_to` and archives ([#5983](https://github.com/ScoopInstaller/Scoop/issues/5983))
- **json:** Serialize jsonpath return ([#5921](https://github.com/ScoopInstaller/Scoop/issues/5921))
- **shim:** Restore original path for JAR cmd ([#6030](https://github.com/ScoopInstaller/Scoop/issues/6030))
### Code Refactoring
- **decompress:** Use 7zip to extract Zstd archive ([#5973](https://github.com/ScoopInstaller/Scoop/issues/5973))
- **install:** Separate archive extraction from downloader ([#5951](https://github.com/ScoopInstaller/Scoop/issues/5951))
- **install:** Replace 'run_(un)installer()' with 'Invoke-Installer()' ([#5968](https://github.com/ScoopInstaller/Scoop/issues/5968), [#5971](https://github.com/ScoopInstaller/Scoop/issues/5971))
## [v0.4.2](https://github.com/ScoopInstaller/Scoop/compare/v0.4.1...v0.4.2) - 2024-05-14
### Bug Fixes
- **autoupdate:** Copy `PSCustomObject`-type properties within `substitute()` to prevent reference changes ([#5934](https://github.com/ScoopInstaller/Scoop/issues/5934), [#5962](https://github.com/ScoopInstaller/Scoop/issues/5962))
- **core:** Fix `Invoke-ExternalCommand` quoting rules ([#5945](https://github.com/ScoopInstaller/Scoop/issues/5945))
- **system:** Fix argument passing to `Split-PathLikeEnvVar()` in deprecated `strip_path()` ([#5937](https://github.com/ScoopInstaller/Scoop/issues/5937))
## [v0.4.1](https://github.com/ScoopInstaller/Scoop/compare/v0.4.0...v0.4.1) - 2024-04-25
### Bug Fixes
- **core:** Fix `Invoke-ExternalCommand` regression ([#5923](https://github.com/ScoopInstaller/Scoop/issues/5923))
## [v0.4.0](https://github.com/ScoopInstaller/Scoop/compare/v0.3.1...v0.4.0) - 2024-04-18
### Features ### Features
- **scoop-update:** Add support for parallel syncing buckets in PowerShell 7 and improve output ([#5122](https://github.com/ScoopInstaller/Scoop/issues/5122)) - **scoop-update:** Add support for parallel syncing buckets in PowerShell 7 and improve output ([#5122](https://github.com/ScoopInstaller/Scoop/issues/5122))
- **bucket:** Switch nirsoft bucket to ScoopInstaller/Nirsoft ([#5328](https://github.com/ScoopInstaller/Scoop/issues/5328)) - **testing:** Add ability to test manifests inside a Windows sandbox ([#5349](https://github.com/(https://github.com/ScoopInstaller/Scoop/pulls/5349))
- **bucket:** Make official buckets higher priority ([#5398](https://github.com/ScoopInstaller/Scoop/issues/5398))
- **config:** Support portable config file ([#5369](https://github.com/ScoopInstaller/Scoop/issues/5369))
- **core:** Add `-Quiet` switch for `Invoke-ExternalCommand` ([#5346](https://github.com/ScoopInstaller/Scoop/issues/5346))
- **core:** Allow global install of PowerShell modules ([#5611](https://github.com/ScoopInstaller/Scoop/issues/5611))
- **path:** Isolate Scoop apps' PATH ([#5840](https://github.com/ScoopInstaller/Scoop/issues/5840))
### Bug Fixes ### Bug Fixes
- **scoop-alias:** Prevent overwrite existing file when adding alias ([#5577](https://github.com/ScoopInstaller/Scoop/issues/5577))
- **scoop-checkup:** Skip defender check in Windows Sandbox ([#5519](https://github.com/ScoopInstaller/Scoop/issues/5519))
- **scoop-checkup:** Change the message level of helpers from ERROR to WARN ([#5614](https://github.com/ScoopInstaller/Scoop/issues/5614))
- **scoop-checkup:** Don't throw 7zip error when external 7zip is used ([#5703](https://github.com/ScoopInstaller/Scoop/issues/5703))
- **scoop-(un)hold:** Correct output the messages when manifest not found, (already|not) held ([#5519](https://github.com/ScoopInstaller/Scoop/issues/5519))
- **scoop-info:** Fix errors in file size collection when `--verbose` ([#5352](https://github.com/ScoopInstaller/Scoop/issues/5352))
- **scoop-reset:** Don't abort when multiple apps are passed and an app is running ([#5687](https://github.com/ScoopInstaller/Scoop/issues/5687))
- **scoop-update:** Change error message to a better instruction ([#5677](https://github.com/ScoopInstaller/Scoop/issues/5677))
- **scoop-virustotal:** Fix `scoop-virustotal` when `--all` has been passed without app ([#5593](https://github.com/ScoopInstaller/Scoop/issues/5593))
- **scoop-virustotal:** Fix the issue that escape character not available in PowerShell 5.1 ([#5870](https://github.com/ScoopInstaller/Scoop/issues/5870))
- **autoupdate:** Fix file hash extraction ([#5295](https://github.com/ScoopInstaller/Scoop/issues/5295))
- **autoupdate:** Fix bug that 'WebClient' doesn't auto-extract 'gzip' ([#5901](https://github.com/ScoopInstaller/Scoop/issues/5901))
- **buckets:** Avoid error messages for unexpected dir ([#5549](https://github.com/ScoopInstaller/Scoop/issues/5549))
- **config:** Warn users about misconfigured GitHub token ([#5777](https://github.com/ScoopInstaller/Scoop/issues/5777))
- **core:** Fix scripts' calling parameters ([#5365](https://github.com/ScoopInstaller/Scoop/issues/5365))
- **core:** Fix `is_in_dir` under Unix ([#5391](https://github.com/ScoopInstaller/Scoop/issues/5391))
- **core:** Rewrite config file when needed ([#5439](https://github.com/ScoopInstaller/Scoop/issues/5439))
- **core:** Prevents leaking HTTP(S)_PROXY env vars to current sessions after Invoke-Git in parallel execution ([#5436](https://github.com/ScoopInstaller/Scoop/issues/5436))
- **core:** Handle scoop aliases and broken(edited,copied) shim ([#5551](https://github.com/ScoopInstaller/Scoop/issues/5551))
- **core:** Avoid error messages when deleting non-existent environment variable ([#5547](https://github.com/ScoopInstaller/Scoop/issues/5547))
- **core:** Use relative path as fallback of `$scoopdir` ([#5544](https://github.com/ScoopInstaller/Scoop/issues/5544))
- **core:** Fix detection of Git ([#5545](https://github.com/ScoopInstaller/Scoop/issues/5545))
- **core:** Do not call `scoop` externally from inside the code ([#5695](https://github.com/ScoopInstaller/Scoop/issues/5695))
- **core:** Fix arguments parsing method of `Invoke-ExternalCommand()` ([#5839](https://github.com/ScoopInstaller/Scoop/issues/5839))
- **decompress:** Exclude '*.nsis' that may cause error ([#5294](https://github.com/ScoopInstaller/Scoop/issues/5294)) - **decompress:** Exclude '*.nsis' that may cause error ([#5294](https://github.com/ScoopInstaller/Scoop/issues/5294))
- **decompress:** Remove unused parent dir w/ 'extract_dir' ([#5682](https://github.com/ScoopInstaller/Scoop/issues/5682)) - **autoupdate:** Fix file hash extraction ([#5295](https://github.com/ScoopInstaller/Scoop/issues/5295))
- **decompress:** Use `wix.exe` in WiX Toolset v4+ as primary extractor of `Expand-DarkArchive()` ([#5871](https://github.com/ScoopInstaller/Scoop/issues/5871))
- **env:** Avoid automatic expansion of `%%` in env ([#5395](https://github.com/ScoopInstaller/Scoop/issues/5395), [#5452](https://github.com/ScoopInstaller/Scoop/issues/5452), [#5631](https://github.com/ScoopInstaller/Scoop/issues/5631))
- **getopt:** Stop split arguments in `getopt()` and ensure array by explicit arguments type ([#5326](https://github.com/ScoopInstaller/Scoop/issues/5326))
- **install:** Fix download from private GitHub repositories ([#5361](https://github.com/ScoopInstaller/Scoop/issues/5361))
- **install:** Avoid error when unlinking non-existent junction/hardlink ([#5552](https://github.com/ScoopInstaller/Scoop/issues/5552))
- **manifest:** Correct source of manifest ([#5575](https://github.com/ScoopInstaller/Scoop/issues/5575))
- **shim:** Remove console window for GUI applications ([#5559](https://github.com/ScoopInstaller/Scoop/issues/5559))
- **shim:** Use bash executable directly ([#5433](https://github.com/ScoopInstaller/Scoop/issues/5433))
- **shim:** Check literal path in `Get-ShimPath` ([#5680](https://github.com/ScoopInstaller/Scoop/issues/5680))
- **shim:** Avoid unexpected output of `list` subcommand ([#5681](https://github.com/ScoopInstaller/Scoop/issues/5681))
- **shim:** Allow GUI applications to attach to the shell's console when launched using the GUI shim ([#5721](https://github.com/ScoopInstaller/Scoop/issues/5721))
- **shim:** Run JAR file from app's root directory ([#5872](https://github.com/ScoopInstaller/Scoop/issues/5872))
- **shortcuts:** Output correctly formatted path ([#5333](https://github.com/ScoopInstaller/Scoop/issues/5333)) - **shortcuts:** Output correctly formatted path ([#5333](https://github.com/ScoopInstaller/Scoop/issues/5333))
- **update/uninstall:** Remove items from PATH correctly ([#5833](https://github.com/ScoopInstaller/Scoop/issues/5833))
### Performance Improvements
- **scoop-search:** Improve performance for local search ([#5644](https://github.com/ScoopInstaller/Scoop/issues/5644))
- **scoop-update:** Check for running process before wasting time on download ([#5799](https://github.com/ScoopInstaller/Scoop/issues/5799))
- **decompress:** Disable progress bar to improve `Expand-Archive` performance ([#5410](https://github.com/ScoopInstaller/Scoop/issues/5410))
- **shim:** Update kiennq-shim to v3.1.1 ([#5841](https://github.com/ScoopInstaller/Scoop/issues/5841), [#5847](https://github.com/ScoopInstaller/Scoop/issues/5847))
### Code Refactoring ### Code Refactoring
- **git:** Use Invoke-Git() with direct path to git.exe to prevent spawning shim subprocesses ([#5122](https://github.com/ScoopInstaller/Scoop/issues/5122))
- **scoop-download:** Output more detailed manifest information ([#5277](https://github.com/ScoopInstaller/Scoop/issues/5277)) - **scoop-download:** Output more detailed manifest information ([#5277](https://github.com/ScoopInstaller/Scoop/issues/5277))
- **core:** Cleanup some old codes, e.g., msi section and config migration ([#5715](https://github.com/ScoopInstaller/Scoop/issues/5715), [#5824](https://github.com/ScoopInstaller/Scoop/issues/5824))
- **core:** Rewrite and separate path-related functions to `system.ps1` ([#5836](https://github.com/ScoopInstaller/Scoop/issues/5836), [#5858](https://github.com/ScoopInstaller/Scoop/issues/5858), [#5864](https://github.com/ScoopInstaller/Scoop/issues/5864))
- **core:** Get rid of 'fullpath' ([#3533](https://github.com/ScoopInstaller/Scoop/issues/3533))
- **git:** Use Invoke-Git() with direct path to git.exe to prevent spawning shim subprocesses ([#5122](https://github.com/ScoopInstaller/Scoop/issues/5122), [#5375](https://github.com/ScoopInstaller/Scoop/issues/5375))
- **helper:** Remove 7zip's fallback '7zip-zstd' ([#5548](https://github.com/ScoopInstaller/Scoop/issues/5548))
- **shim:** Remove CS shim codebase ([#5903](https://github.com/ScoopInstaller/Scoop/issues/5903))
### Builds
- **checkver:** Read the private_host config variable ([#5381](https://github.com/ScoopInstaller/Scoop/issues/5381))
- **supporting:** Update Json to 13.0.3, Json.Schema to 3.0.15 ([#5835](https://github.com/ScoopInstaller/Scoop/issues/5835))
### Continuous Integration
- **dependabot:** Add dependabot.yml for GitHub Actions ([#5377](https://github.com/ScoopInstaller/Scoop/issues/5377))
- **module:** Update 'psmodulecache' version to 'main' ([#5828](https://github.com/ScoopInstaller/Scoop/issues/5828))
### Tests ### Tests
- **bucket:** Skip manifest validation if no manifest changes ([#5270](https://github.com/ScoopInstaller/Scoop/issues/5270)) - **bucket:** Skip manifest validation if no manifest changes ([#5270](https://github.com/ScoopInstaller/Scoop/issues/5270))
### Documentation
- **scoop-info:** Fix help message([#5445](https://github.com/ScoopInstaller/Scoop/issues/5445))
- **readme:** Improve documentation language ([#5638](https://github.com/ScoopInstaller/Scoop/issues/5638))
## [v0.3.1](https://github.com/ScoopInstaller/Scoop/compare/v0.3.0...v0.3.1) - 2022-11-15 ## [v0.3.1](https://github.com/ScoopInstaller/Scoop/compare/v0.3.0...v0.3.1) - 2022-11-15
### Features ### Features
@@ -198,7 +36,6 @@
- **shim:** Exit if shim creating failed 'cause no git ([#5225](https://github.com/ScoopInstaller/Scoop/issues/5225)) - **shim:** Exit if shim creating failed 'cause no git ([#5225](https://github.com/ScoopInstaller/Scoop/issues/5225))
- **scoop-import:** Add correct architecture argument ([#5210](https://github.com/ScoopInstaller/Scoop/issues/5210)) - **scoop-import:** Add correct architecture argument ([#5210](https://github.com/ScoopInstaller/Scoop/issues/5210))
- **scoop-config:** Output `[DateTime]` as `[String]` ([#5232](https://github.com/ScoopInstaller/Scoop/issues/5232)) - **scoop-config:** Output `[DateTime]` as `[String]` ([#5232](https://github.com/ScoopInstaller/Scoop/issues/5232))
- **shim:** fixed shim add bug related to Resolve-Path ([#5492](https://github.com/ScoopInstaller/Scoop/issues/5492))
### Code Refactoring ### Code Refactoring

105
README.md
View File

@@ -1,17 +1,17 @@
<h1 align="center">Scoop</h1>
<!--<img src="scoop.png" alt="Long live Scoop!"/>-->
<p align="center"> <p align="center">
<a href="https://github.com/ScoopInstaller/Scoop#what-does-scoop-do">Features</a> <!--<img src="scoop.png" alt="Long live Scoop!"/>-->
| <h1 align="center">Scoop</h1>
<a href="https://github.com/ScoopInstaller/Scoop#installation">Installation</a> </p>
| <p align="center">
<a href="https://github.com/ScoopInstaller/Scoop/wiki">Documentation</a> <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>
--- - - -
<p align="center" >
<p align="center">
<a href="https://github.com/ScoopInstaller/Scoop"> <a href="https://github.com/ScoopInstaller/Scoop">
<img src="https://img.shields.io/github/languages/code-size/ScoopInstaller/Scoop.svg" alt="Code Size" /> <img src="https://img.shields.io/github/languages/code-size/ScoopInstaller/Scoop.svg" alt="Code Size" />
</a> </a>
@@ -36,48 +36,43 @@ Scoop is a command-line installer for Windows.
## What does Scoop do? ## What does Scoop do?
Scoop installs apps from the command line with a minimal amount of friction. It: Scoop installs programs from the command line with a minimal amount of friction. It:
- Eliminates [User Account Control](https://learn.microsoft.com/windows/security/application-security/application-control/user-account-control/) (UAC) prompt notifications. - Eliminates permission popup windows
- Hides the graphical user interface (GUI) of wizard-style installers. - Hides GUI wizard-style installers
- Prevents polluting the `PATH` environment variable. Normally, this variable gets cluttered as different apps are installed on the device. - Prevents PATH pollution from installing lots of programs
- Avoids unexpected side effects from installing and uninstalling apps. - Avoids unexpected side-effects from installing and uninstalling programs
- Resolves and installs dependencies automatically. - Finds and installs dependencies automatically
- Performs all the necessary steps to get an app to a working state. - Performs all the extra setup steps itself to get a working program
Scoop is quite script-friendly. Your environment can become the way you like by using repeatable setups. For example: Scoop is very scriptable, so you can run repeatable setups to get your environment just the way you like, e.g.:
```console ```powershell
scoop install sudo scoop install sudo
sudo scoop install 7zip git openssh --global sudo scoop install 7zip git openssh --global
scoop install aria2 curl grep sed less touch scoop install aria2 curl grep sed less touch
scoop install python ruby go perl scoop install python ruby go perl
``` ```
If you have built software that you would like others to use, Scoop is an alternative to building an installer (like MSI or InnoSetup). You just need to compress your app to a `.zip` file and provide a JSON manifest that describes how to install it. 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.
## Installation ## Installation
Run the following commands from a regular (non-admin) PowerShell terminal to install Scoop: Run the following command from a **non-admin** PowerShell to install scoop to its default location `C:\Users\<YOUR USERNAME>\scoop`.
```powershell ```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser iwr -useb get.scoop.sh | iex
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
``` ```
**Note**: The first command makes your device allow running the installation and management scripts. This is necessary because Windows 10 client devices restrict execution of any PowerShell scripts by default. Advanced installation instruction and full documentation of the installer are available in [ScoopInstaller/Install](https://github.com/ScoopInstaller/Install). Please create new issues there if you have questions about the installation.
It will install Scoop to its default location: ## [Documentation](https://github.com/ScoopInstaller/Scoop/wiki)
`C:\Users\<YOUR USERNAME>\scoop`
You can find the complete documentation about the installer, including advanced installation configurations, in [ScoopInstaller/Install](https://github.com/ScoopInstaller/Install). Please create new issues there if you have questions about the installation.
## Multi-connection downloads with `aria2` ## 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. 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.
```console ```powershell
scoop install aria2 scoop install aria2
``` ```
@@ -95,54 +90,54 @@ You can tweak the following `aria2` settings with the `scoop config` command:
## Inspiration ## Inspiration
- [Homebrew](https://brew.sh/) - [Homebrew](http://mxcl.github.io/homebrew/)
- [Sub](https://signalvnoise.com/posts/3264-automating-with-convention-introducing-sub) - [sub](https://github.com/37signals/sub#readme)
## What sort of apps can Scoop install? ## What sort of apps can Scoop install?
The apps that are most likely to get installed fine with Scoop are those referred to as "portable" apps. These apps are compressed files which can run standalone after being extracted. This type of apps does not produce side effects like changing the Windows Registry or placing files outside the app directory. 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.
Scoop also supports installer files and their uninstallation methods. Likewise, it can handle single-file apps and PowerShell scripts. These do not even need to be compressed. See the [runat](https://github.com/ScoopInstaller/Main/blob/master/bucket/runat.json) package for an example: it is simply a GitHub gist. 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.
### Contribute to this project ### Contribute to this project
If you would like to improve Scoop by adding features or fixing bugs, please read our [Contributing Guide](https://github.com/ScoopInstaller/.github/blob/main/.github/CONTRIBUTING.md). If you'd like to improve Scoop by adding features or fixing bugs, please read our [Contributing Guide](https://github.com/ScoopInstaller/.github/blob/main/.github/CONTRIBUTING.md).
### Support this project ### Support this project
If you find Scoop useful and would like to support the ongoing development and maintenance of this project, you can donate here: 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 donations) - [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DM2SUH9EUXSKJ) (one-time donation)
## Known application buckets ## Known application buckets
The following buckets are known to Scoop: The following buckets are known to scoop:
- [main](https://github.com/ScoopInstaller/Main) - Default bucket which contains popular non-GUI apps. - [main](https://github.com/ScoopInstaller/Main) - Default bucket for the most common (mostly CLI) apps
- [extras](https://github.com/ScoopInstaller/Extras) - Apps that do not fit the main bucket's [criteria](https://github.com/ScoopInstaller/Scoop/wiki/Criteria-for-including-apps-in-the-main-bucket). - [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 and freeware video games and game-related tools. - [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. - [nerd-fonts](https://github.com/matthewjberger/scoop-nerd-fonts) - Nerd Fonts
- [nirsoft](https://github.com/ScoopInstaller/Nirsoft) - A collection of over 250+ apps from [Nirsoft](https://nirsoft.net). - [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)
- [sysinternals](https://github.com/niheaven/scoop-sysinternals) - The Sysinternals suite from [Microsoft](https://learn.microsoft.com/sysinternals/). - [sysinternals](https://github.com/niheaven/scoop-sysinternals) - Sysinternals Suite and all individual application from [Microsoft](https://learn.microsoft.com/sysinternals/)
- [java](https://github.com/ScoopInstaller/Java) - A collection of Java development kits (JDKs) and Java runtime engines (JREs), Java's virtual machine debugging tools and Java based runtime engines. - [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.
- [nonportable](https://github.com/ScoopInstaller/Nonportable) - Non-portable apps (may trigger UAC prompts). - [nonportable](https://github.com/ScoopInstaller/Nonportable) - Non-portable apps (may require UAC)
- [php](https://github.com/ScoopInstaller/PHP) - Installers for most versions of PHP. - [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. - [versions](https://github.com/ScoopInstaller/Versions) - Alternative versions of apps found in other buckets
The `main` bucket is installed by default. You can make use of more buckets by typing: The main bucket is installed by default. To add any of the other buckets, type:
```console ```console
scoop bucket add <name> scoop bucket add bucketname
``` ```
For example, to add the `extras` bucket, type: For example, to add the extras bucket, type:
```console ```console
scoop bucket add extras scoop bucket add extras
``` ```
You would be able to install apps from the `extras` bucket now.
## Other application buckets ## Other application buckets
Many other application buckets hosted on GitHub can be found on [ScoopSearch](https://scoop.sh/) or via [other search engines](https://rasa.github.io/scoop-directory/#other-search-engines). Many other application buckets hosted on Github can be found in the [Scoop Directory](https://rasa.github.io/scoop-directory/) or via [other search engines](https://rasa.github.io/scoop-directory/#other-search-engines).

View File

@@ -46,7 +46,7 @@ param(
. "$PSScriptRoot\..\lib\autoupdate.ps1" . "$PSScriptRoot\..\lib\autoupdate.ps1"
. "$PSScriptRoot\..\lib\json.ps1" . "$PSScriptRoot\..\lib\json.ps1"
. "$PSScriptRoot\..\lib\versions.ps1" . "$PSScriptRoot\..\lib\versions.ps1"
. "$PSScriptRoot\..\lib\download.ps1" . "$PSScriptRoot\..\lib\install.ps1"
$Dir = Convert-Path $Dir $Dir = Convert-Path $Dir
if ($ForceUpdate) { $Update = $true } if ($ForceUpdate) { $Update = $true }
@@ -121,7 +121,7 @@ foreach ($current in $MANIFESTS) {
Invoke-CachedDownload $current.app $version $_ $null $null -use_cache:$UseCache Invoke-CachedDownload $current.app $version $_ $null $null -use_cache:$UseCache
$to_check = cache_path $current.app $version $_ $to_check = fullpath (cache_path $current.app $version $_)
$actual_hash = (Get-FileHash -Path $to_check -Algorithm $algorithm).Hash.ToLower() $actual_hash = (Get-FileHash -Path $to_check -Algorithm $algorithm).Hash.ToLower()
# Append type of algorithm to both expected and actual if it's not sha256 # Append type of algorithm to both expected and actual if it's not sha256
@@ -146,7 +146,7 @@ foreach ($current in $MANIFESTS) {
Write-Host "$($current.app): " -NoNewline Write-Host "$($current.app): " -NoNewline
Write-Host 'Mismatch found ' -ForegroundColor Red Write-Host 'Mismatch found ' -ForegroundColor Red
$mismatched | ForEach-Object { $mismatched | ForEach-Object {
$file = cache_path $current.app $version $current.urls[$_] $file = fullpath (cache_path $current.app $version $current.urls[$_])
Write-Host "`tURL:`t`t$($current.urls[$_])" Write-Host "`tURL:`t`t$($current.urls[$_])"
if (Test-Path $file) { if (Test-Path $file) {
Write-Host "`tFirst bytes:`t$((get_magic_bytes_pretty $file ' ').ToUpper())" Write-Host "`tFirst bytes:`t$((get_magic_bytes_pretty $file ' ').ToUpper())"

View File

@@ -28,7 +28,7 @@ param(
. "$PSScriptRoot\..\lib\core.ps1" . "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1" . "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\download.ps1" . "$PSScriptRoot\..\lib\install.ps1"
$Dir = Convert-Path $Dir $Dir = Convert-Path $Dir
$Queue = @() $Queue = @()

View File

@@ -73,7 +73,7 @@ param(
. "$PSScriptRoot\..\lib\buckets.ps1" . "$PSScriptRoot\..\lib\buckets.ps1"
. "$PSScriptRoot\..\lib\json.ps1" . "$PSScriptRoot\..\lib\json.ps1"
. "$PSScriptRoot\..\lib\versions.ps1" . "$PSScriptRoot\..\lib\versions.ps1"
. "$PSScriptRoot\..\lib\download.ps1" . "$PSScriptRoot\..\lib\install.ps1" # needed for hash generation
if ($App -ne '*' -and (Test-Path $App -PathType Leaf)) { if ($App -ne '*' -and (Test-Path $App -PathType Leaf)) {
$Dir = Split-Path $App $Dir = Split-Path $App
@@ -226,21 +226,15 @@ $Queue | ForEach-Object {
$url = substitute $url $substitutions $url = substitute $url $substitutions
$state = New-Object psobject @{ $state = New-Object psobject @{
app = $name app = $name;
file = $file file = $file;
url = $url url = $url;
regex = $regex regex = $regex;
json = $json json = $json;
jsonpath = $jsonpath jsonpath = $jsonpath;
xpath = $xpath xpath = $xpath;
reverse = $reverse reverse = $reverse;
replace = $replace replace = $replace;
}
get_config PRIVATE_HOSTS | Where-Object { $_ -ne $null -and $url -match $_.match } | ForEach-Object {
(ConvertFrom-StringData -StringData $_.Headers).GetEnumerator() | ForEach-Object {
$wc.Headers[$_.Key] = $_.Value
}
} }
$wc.Headers.Add('Referer', (strip_filename $url)) $wc.Headers.Add('Referer', (strip_filename $url))
@@ -260,7 +254,6 @@ while ($in_progress -gt 0) {
$in_progress-- $in_progress--
$state = $ev.SourceEventArgs.UserState $state = $ev.SourceEventArgs.UserState
$result = $ev.SourceEventArgs.Result
$app = $state.app $app = $state.app
$file = $state.file $file = $state.file
$json = $state.json $json = $state.json
@@ -275,7 +268,7 @@ while ($in_progress -gt 0) {
$ver = $Version $ver = $Version
if (!$ver) { if (!$ver) {
if (!$regexp -and $replace) { if (!$regex -and $replace) {
next "'replace' requires 're' or 'regex'" next "'replace' requires 're' or 'regex'"
continue continue
} }
@@ -286,23 +279,15 @@ while ($in_progress -gt 0) {
} }
if ($url) { if ($url) {
$ms = New-Object System.IO.MemoryStream $page = (Get-Encoding($wc)).GetString($ev.SourceEventArgs.Result)
$ms.Write($result, 0, $result.Length)
$ms.Seek(0, 0) | Out-Null
if ($result[0] -eq 0x1F -and $result[1] -eq 0x8B) {
$ms = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
} }
$page = (New-Object System.IO.StreamReader($ms, (Get-Encoding $wc))).ReadToEnd()
}
$source = $url
if ($script) { if ($script) {
$page = Invoke-Command ([scriptblock]::Create($script -join "`r`n")) $page = Invoke-Command ([scriptblock]::Create($script -join "`r`n"))
$source = 'the output of script'
} }
if ($jsonpath) { if ($jsonpath) {
# Return only a single value if regex is absent # Return only a single value if regex is absent
$noregex = [String]::IsNullOrEmpty($regexp) $noregex = [String]::IsNullOrEmpty($regex)
# If reverse is ON and regex is ON, # If reverse is ON and regex is ON,
# Then reverse would have no effect because regex handles reverse # Then reverse would have no effect because regex handles reverse
# on its own # on its own
@@ -312,7 +297,7 @@ while ($in_progress -gt 0) {
$ver = json_path_legacy $page $jsonpath $ver = json_path_legacy $page $jsonpath
} }
if (!$ver) { if (!$ver) {
next "couldn't find '$jsonpath' in $source" next "couldn't find '$jsonpath' in $url"
continue continue
} }
} }
@@ -334,7 +319,7 @@ while ($in_progress -gt 0) {
# Getting version from XML, using XPath # Getting version from XML, using XPath
$ver = $xml.SelectSingleNode($xpath, $nsmgr).'#text' $ver = $xml.SelectSingleNode($xpath, $nsmgr).'#text'
if (!$ver) { if (!$ver) {
next "couldn't find '$($xpath -replace 'ns:', '')' in $source" next "couldn't find '$($xpath -replace 'ns:', '')' in $url"
continue continue
} }
} }
@@ -350,31 +335,31 @@ while ($in_progress -gt 0) {
} }
if ($regexp) { if ($regexp) {
$re = New-Object System.Text.RegularExpressions.Regex($regexp) $regex = New-Object System.Text.RegularExpressions.Regex($regexp)
if ($reverse) { if ($reverse) {
$match = $re.Matches($page) | Select-Object -Last 1 $match = $regex.Matches($page) | Select-Object -Last 1
} else { } else {
$match = $re.Matches($page) | Select-Object -First 1 $match = $regex.Matches($page) | Select-Object -First 1
} }
if ($match -and $match.Success) { if ($match -and $match.Success) {
$matchesHashtable = @{} $matchesHashtable = @{}
$re.GetGroupNames() | ForEach-Object { $matchesHashtable.Add($_, $match.Groups[$_].Value) } $regex.GetGroupNames() | ForEach-Object { $matchesHashtable.Add($_, $match.Groups[$_].Value) }
$ver = $matchesHashtable['1'] $ver = $matchesHashtable['1']
if ($replace) { if ($replace) {
$ver = $re.Replace($match.Value, $replace) $ver = $regex.Replace($match.Value, $replace)
} }
if (!$ver) { if (!$ver) {
$ver = $matchesHashtable['version'] $ver = $matchesHashtable['version']
} }
} else { } else {
next "couldn't match '$regexp' in $source" next "couldn't match '$regexp' in $url"
continue continue
} }
} }
if (!$ver) { if (!$ver) {
next "couldn't find new version in $source" next "couldn't find new version in $url"
continue continue
} }
} }

View File

@@ -23,7 +23,6 @@ param(
. "$PSScriptRoot\..\lib\core.ps1" . "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1" . "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\description.ps1" . "$PSScriptRoot\..\lib\description.ps1"
. "$PSScriptRoot\..\lib\download.ps1"
$Dir = Convert-Path $Dir $Dir = Convert-Path $Dir
$Queue = @() $Queue = @()

237
bin/sandbox.ps1 Normal file
View File

@@ -0,0 +1,237 @@
# SPDX-License-Identifier: MIT
# Portions Copyright (c) Microsoft Corporation. All rights reserved.
# Parse arguments
Param(
[Parameter(Position = 0, HelpMessage = 'The Manifest to install in the Sandbox.')]
[String] $Manifest,
[Parameter(Position = 1, HelpMessage = 'Options to pass to scoop.')]
[String] $Options,
[Parameter(Position = 2, HelpMessage = 'The script to run in the Sandbox.')]
[ScriptBlock] $Script,
[Parameter(HelpMessage = 'The folder to map in the Sandbox.')]
[String] $MapFolder = $pwd
)
$ErrorActionPreference = 'Stop'
$mapFolder = (Resolve-Path -Path $MapFolder).Path
if (-Not (Test-Path -Path $mapFolder -PathType Container)) {
Write-Error -Category InvalidArgument -Message 'The provided MapFolder is not a folder.'
}
# Check if Windows Sandbox is enabled
if (-Not (Get-Command 'WindowsSandbox' -ErrorAction SilentlyContinue)) {
Write-Error -Category NotInstalled -Message @'
Windows Sandbox does not seem to be available. Check the following URL for prerequisites and further details:
https://docs.microsoft.com/windows/security/threat-protection/windows-sandbox/windows-sandbox-overview
You can run the following command in an elevated PowerShell for enabling Windows Sandbox:
$ Enable-WindowsOptionalFeature -Online -FeatureName 'Containers-DisposableClientVM'
'@
}
# Close Windows Sandbox
$sandbox = Get-Process 'WindowsSandboxClient' -ErrorAction SilentlyContinue
if ($sandbox) {
Write-Host '--> Closing Windows Sandbox'
$sandbox | Stop-Process
Start-Sleep -Seconds 5
Write-Host
}
Remove-Variable sandbox
# Initialize Temp Folder
$tempFolderName = 'SandboxTest'
$tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath $tempFolderName
Remove-Item $tempFolder -Force -Recurse
New-Item $tempFolder -ItemType Directory | Out-Null
if (-Not [String]::IsNullOrWhiteSpace($Manifest)) {
Copy-Item -Path $Manifest -Recurse -Destination $tempFolder
}
if ($null -eq $env:SCOOP_HOME) { $env:SCOOP_HOME = "$env:USERPROFILE\scoop" }
$scoopCache = $env:SCOOP_HOME + '\cache'
Write-Host "Copying $scoopCache to $tempFolder\cache"
Copy-Item -Path $scoopCache -Recurse -Destination $tempFolder | Out-Null
$userprofileInSandbox = 'C:\Users\WDAGUtilityAccount'
$desktopInSandbox = $userprofileInSandbox + '\Desktop'
$sandboxTestInSandbox = $desktopInSandbox + '\' + $tempFolderName
$copiedCacheInSandbox = $sandboxTestInSandbox + "\cache"
$scoopCacheInSandbox = $userprofileInSandbox + "\scoop\cache"
# Create Bootstrap script
# See: https://stackoverflow.com/a/22670892/12156188
$bootstrapPs1Content = @'
function Update-EnvironmentVariables {
foreach($level in "Machine","User") {
[Environment]::GetEnvironmentVariables($level).GetEnumerator() | % {
# For Path variables, append the new values, if they're not already in there
if($_.Name -match 'Path$') {
$_.Value = ($((Get-Content "Env:$($_.Name)") + ";$($_.Value)") -split ';' | Select -unique) -join ';'
}
$_
} | Set-Content -Path { "Env:$($_.Name)" }
}
}
function Get-ARPTable {
$registry_paths = @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*','HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*')
return Get-ItemProperty $registry_paths -ErrorAction SilentlyContinue |
Select-Object DisplayName, DisplayVersion, Publisher, @{N='ProductCode'; E={$_.PSChildName}} |
Where-Object {$null -ne $_.DisplayName }
}
'@
$bootstrapPs1Content += @"
Write-Host @'
--> Installing Scoop, 7zip, git, innounp, dark and lessmsi
'@
`$ProgressPreference = 'SilentlyContinue'
irm get.scoop.sh -outfile 'install.ps1'
.\install.ps1 -RunAsAdmin
Update-EnvironmentVariables
xcopy /I /Q /Y $copiedCacheInSandbox\*.* $scoopCacheInSandbox\
scoop install --no-update-scoop main/7zip
scoop install --no-update-scoop main/git
scoop install --no-update-scoop main/innounp
scoop install --no-update-scoop main/dark
scoop install --no-update-scoop main/lessmsi
Write-Host @'
Tip: you can type 'Update-EnvironmentVariables' to update your environment variables, such as after installing a new software.
'@
"@
if (-Not [String]::IsNullOrWhiteSpace($Manifest)) {
$manifestFileName = Split-Path $Manifest -Leaf
$manifestPathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $manifestFileName)
$bootstrapPs1Content += @"
Write-Host @'
--> Saving current ARP entries
'@
`$originalARP = Get-ARPTable
Write-Host @'
--> Running: scoop install $Options $Manifest
'@
scoop install $Options --no-update-scoop $manifestPathInSandbox
Write-Host @'
--> Refreshing environment variables
'@
Update-EnvironmentVariables
Write-Host @'
--> Comparing ARP entries
'@
(Compare-Object (Get-ARPTable) `$originalARP -Property DisplayName,DisplayVersion,Publisher,ProductCode)| Select-Object -Property * -ExcludeProperty SideIndicator | Format-Table
"@
}
if (-Not [String]::IsNullOrWhiteSpace($Script)) {
$bootstrapPs1Content += @"
Write-Host @'
--> Running the following script:
{
$Script
}
'@
$Script
"@
}
$bootstrapPs1Content += @'
Write-Host
'@
$bootstrapPs1FileName = 'Bootstrap.ps1'
$bootstrapPs1Content | Out-File (Join-Path -Path $tempFolder -ChildPath $bootstrapPs1FileName)
# Create Wsb file
$bootstrapPs1InSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $bootstrapPs1FileName)
$mapFolderInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Split-Path -Path $mapFolder -Leaf)
$sandboxTestWsbContent = @"
<Configuration>
<MappedFolders>
<MappedFolder>
<HostFolder>$tempFolder</HostFolder>
<ReadOnly>true</ReadOnly>
</MappedFolder>
<MappedFolder>
<HostFolder>$mapFolder</HostFolder>
</MappedFolder>
</MappedFolders>
<LogonCommand>
<Command>PowerShell Start-Process PowerShell -WindowStyle Maximized -WorkingDirectory '$mapFolderInSandbox' -ArgumentList '-ExecutionPolicy Bypass -NoExit -NoLogo -File $bootstrapPs1InSandbox'</Command>
</LogonCommand>
</Configuration>
"@
$sandboxTestWsbFileName = 'SandboxTest.wsb'
$sandboxTestWsbFile = Join-Path -Path $tempFolder -ChildPath $sandboxTestWsbFileName
$sandboxTestWsbContent | Out-File $sandboxTestWsbFile
Write-Host @"
--> Starting Windows Sandbox, and:
- Mounting the following directories:
- $tempFolder as read-only
- $mapFolder as read-and-write
- Installing Scoop
"@
if (-Not [String]::IsNullOrWhiteSpace($Manifest)) {
Write-Host @"
- Installing the Manifest $manifestFileName
- Refreshing environment variables
- Comparing ARP Entries
"@
}
if (-Not [String]::IsNullOrWhiteSpace($Script)) {
Write-Host @"
- Running the following script:
{
$Script
}
"@
}
Write-Host
WindowsSandbox $SandboxTestWsbFile

View File

@@ -12,7 +12,6 @@ param(
) )
. "$PSScriptRoot\..\lib\core.ps1" . "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\system.ps1"
. "$PSScriptRoot\..\lib\install.ps1" . "$PSScriptRoot\..\lib\install.ps1"
. "$PSScriptRoot\..\lib\shortcuts.ps1" . "$PSScriptRoot\..\lib\shortcuts.ps1"
. "$PSScriptRoot\..\lib\versions.ps1" . "$PSScriptRoot\..\lib\versions.ps1"
@@ -42,7 +41,7 @@ function do_uninstall($app, $global) {
$architecture = $install.architecture $architecture = $install.architecture
Write-Output "Uninstalling '$app'" Write-Output "Uninstalling '$app'"
Invoke-Installer -Path $dir -Manifest $manifest -ProcessorArchitecture $architecture -Uninstall run_uninstaller $manifest $architecture $dir
rm_shims $app $manifest $global $architecture rm_shims $app $manifest $global $architecture
# If a junction was used during install, that will have been used # If a junction was used during install, that will have been used
@@ -99,9 +98,7 @@ if ($purge) {
if ($global) { keep_onlypersist $globaldir } if ($global) { keep_onlypersist $globaldir }
} }
Remove-Path -Path (shimdir $global) -Global:$global remove_from_path (shimdir $false)
if (get_config USE_ISOLATED_PATH) { if ($global) { remove_from_path (shimdir $true) }
Remove-Path -Path ('%' + $scoopPathEnvVar + '%') -Global:$global
}
success 'Scoop has been uninstalled.' success 'Scoop has been uninstalled.'

View File

@@ -2,7 +2,7 @@
"main": "https://github.com/ScoopInstaller/Main", "main": "https://github.com/ScoopInstaller/Main",
"extras": "https://github.com/ScoopInstaller/Extras", "extras": "https://github.com/ScoopInstaller/Extras",
"versions": "https://github.com/ScoopInstaller/Versions", "versions": "https://github.com/ScoopInstaller/Versions",
"nirsoft": "https://github.com/ScoopInstaller/Nirsoft", "nirsoft": "https://github.com/kodybrown/scoop-nirsoft",
"sysinternals": "https://github.com/niheaven/scoop-sysinternals", "sysinternals": "https://github.com/niheaven/scoop-sysinternals",
"php": "https://github.com/ScoopInstaller/PHP", "php": "https://github.com/ScoopInstaller/PHP",
"nerd-fonts": "https://github.com/matthewjberger/scoop-nerd-fonts", "nerd-fonts": "https://github.com/matthewjberger/scoop-nerd-fonts",

View File

@@ -1,22 +1,4 @@
# Must included with 'json.ps1' # Must included with 'json.ps1'
function format_hash([String] $hash) {
$hash = $hash.toLower()
if ($hash -like 'sha256:*') {
$hash = $hash.Substring(7) # Remove prefix 'sha256:'
}
switch ($hash.Length) {
32 { $hash = "md5:$hash" } # md5
40 { $hash = "sha1:$hash" } # sha1
64 { $hash = $hash } # sha256
128 { $hash = "sha512:$hash" } # sha512
default { $hash = $null }
}
return $hash
}
function find_hash_in_rdf([String] $url, [String] $basename) { function find_hash_in_rdf([String] $url, [String] $basename) {
$xml = $null $xml = $null
try { try {
@@ -55,13 +37,7 @@ function find_hash_in_textfile([String] $url, [Hashtable] $substitutions, [Strin
$wc.Headers.Add('Referer', (strip_filename $url)) $wc.Headers.Add('Referer', (strip_filename $url))
$wc.Headers.Add('User-Agent', (Get-UserAgent)) $wc.Headers.Add('User-Agent', (Get-UserAgent))
$data = $wc.DownloadData($url) $data = $wc.DownloadData($url)
$ms = New-Object System.IO.MemoryStream $hashfile = (Get-Encoding($wc)).GetString($data)
$ms.Write($data, 0, $data.Length)
$ms.Seek(0, 0) | Out-Null
if ($data[0] -eq 0x1F -and $data[1] -eq 0x8B) {
$ms = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
}
$hashfile = (New-Object System.IO.StreamReader($ms, (Get-Encoding $wc))).ReadToEnd()
} catch [system.net.webexception] { } catch [system.net.webexception] {
Write-Host $_ -ForegroundColor DarkRed Write-Host $_ -ForegroundColor DarkRed
Write-Host "URL $url is not valid" -ForegroundColor DarkRed Write-Host "URL $url is not valid" -ForegroundColor DarkRed
@@ -117,13 +93,7 @@ function find_hash_in_json([String] $url, [Hashtable] $substitutions, [String] $
$wc.Headers.Add('Referer', (strip_filename $url)) $wc.Headers.Add('Referer', (strip_filename $url))
$wc.Headers.Add('User-Agent', (Get-UserAgent)) $wc.Headers.Add('User-Agent', (Get-UserAgent))
$data = $wc.DownloadData($url) $data = $wc.DownloadData($url)
$ms = New-Object System.IO.MemoryStream $json = (Get-Encoding($wc)).GetString($data)
$ms.Write($data, 0, $data.Length)
$ms.Seek(0, 0) | Out-Null
if ($data[0] -eq 0x1F -and $data[1] -eq 0x8B) {
$ms = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
}
$json = (New-Object System.IO.StreamReader($ms, (Get-Encoding $wc))).ReadToEnd()
} catch [System.Net.WebException] { } catch [System.Net.WebException] {
Write-Host $_ -ForegroundColor DarkRed Write-Host $_ -ForegroundColor DarkRed
Write-Host "URL $url is not valid" -ForegroundColor DarkRed Write-Host "URL $url is not valid" -ForegroundColor DarkRed
@@ -145,13 +115,7 @@ function find_hash_in_xml([String] $url, [Hashtable] $substitutions, [String] $x
$wc.Headers.Add('Referer', (strip_filename $url)) $wc.Headers.Add('Referer', (strip_filename $url))
$wc.Headers.Add('User-Agent', (Get-UserAgent)) $wc.Headers.Add('User-Agent', (Get-UserAgent))
$data = $wc.DownloadData($url) $data = $wc.DownloadData($url)
$ms = New-Object System.IO.MemoryStream $xml = [xml]((Get-Encoding($wc)).GetString($data))
$ms.Write($data, 0, $data.Length)
$ms.Seek(0, 0) | Out-Null
if ($data[0] -eq 0x1F -and $data[1] -eq 0x8B) {
$ms = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress)
}
$xml = [xml]((New-Object System.IO.StreamReader($ms, (Get-Encoding $wc))).ReadToEnd())
} catch [system.net.webexception] { } catch [system.net.webexception] {
Write-Host $_ -ForegroundColor DarkRed Write-Host $_ -ForegroundColor DarkRed
Write-Host "URL $url is not valid" -ForegroundColor DarkRed Write-Host "URL $url is not valid" -ForegroundColor DarkRed
@@ -264,10 +228,6 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
$hashmode = 'sourceforge' $hashmode = 'sourceforge'
} }
if (!$hashfile_url -and $url -match 'https:\/\/github\.com\/(?<owner>[^\/]+)\/(?<repo>[^\/]+)\/releases\/download\/[^\/]+\/[^\/]+') {
$hashmode = 'github'
}
switch ($hashmode) { switch ($hashmode) {
'extract' { 'extract' {
$hash = find_hash_in_textfile $hashfile_url $substitutions $regex $hash = find_hash_in_textfile $hashfile_url $substitutions $regex
@@ -295,10 +255,6 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
$hashfile_url = (strip_filename (strip_fragment "https://sourceforge.net/projects/$($matches['project'])/files/$($matches['file'])")).TrimEnd('/') $hashfile_url = (strip_filename (strip_fragment "https://sourceforge.net/projects/$($matches['project'])/files/$($matches['file'])")).TrimEnd('/')
$hash = find_hash_in_textfile $hashfile_url $substitutions '"$basename":.*?"sha1":\s*"([a-fA-F0-9]{40})"' $hash = find_hash_in_textfile $hashfile_url $substitutions '"$basename":.*?"sha1":\s*"([a-fA-F0-9]{40})"'
} }
'github' {
$hashfile_url = "https://api.github.com/repos/$($matches['owner'])/$($matches['repo'])/releases"
$hash = find_hash_in_json $hashfile_url $substitutions ("$..assets[?(@.browser_download_url == '" + $url + "')].digest")
}
} }
if ($hash) { if ($hash) {
@@ -322,7 +278,7 @@ function get_hash_for_app([String] $app, $config, [String] $version, [String] $u
Write-Host "URL $url is not valid" -ForegroundColor DarkRed Write-Host "URL $url is not valid" -ForegroundColor DarkRed
return $null return $null
} }
$file = cache_path $app $version $url $file = fullpath (cache_path $app $version $url)
$hash = (Get-FileHash -Path $file -Algorithm SHA256).Hash.ToLower() $hash = (Get-FileHash -Path $file -Algorithm SHA256).Hash.ToLower()
Write-Host 'Computed hash: ' -ForegroundColor DarkYellow -NoNewline Write-Host 'Computed hash: ' -ForegroundColor DarkYellow -NoNewline
Write-Host $hash -ForegroundColor Green Write-Host $hash -ForegroundColor Green

View File

@@ -58,18 +58,10 @@ function Get-LocalBucket {
.SYNOPSIS .SYNOPSIS
List all local buckets. List all local buckets.
#> #>
$bucketNames = [System.Collections.Generic.List[String]](Get-ChildItem -Path $bucketsdir -Directory).Name $bucketNames = (Get-ChildItem -Path $bucketsdir -Directory).Name
if ($null -eq $bucketNames) { if ($null -eq $bucketNames) {
return @() # Return a zero-length list instead of $null. return @() # Return a zero-length list instead of $null.
} else { } else {
$knownBuckets = known_buckets
for ($i = $knownBuckets.Count - 1; $i -ge 0 ; $i--) {
$name = $knownBuckets[$i]
if ($bucketNames.Contains($name)) {
[void]$bucketNames.Remove($name)
$bucketNames.Insert(0, $name)
}
}
return $bucketNames return $bucketNames
} }
} }
@@ -108,10 +100,10 @@ function list_buckets {
$path = Find-BucketDirectory $_ -Root $path = Find-BucketDirectory $_ -Root
if ((Test-Path (Join-Path $path '.git')) -and (Get-Command git -ErrorAction SilentlyContinue)) { if ((Test-Path (Join-Path $path '.git')) -and (Get-Command git -ErrorAction SilentlyContinue)) {
$bucket.Source = Invoke-Git -Path $path -ArgumentList @('config', 'remote.origin.url') $bucket.Source = Invoke-Git -Path $path -ArgumentList @('config', 'remote.origin.url')
$bucket.Updated = Invoke-Git -Path $path -ArgumentList @('log', '--format=%aD', '-n', '1') | Get-Date $bucket.Updated = Invoke-Git -Path $path -ArgumentList @('log', "--format='%aD'", '-n', '1')
} else { } else {
$bucket.Source = friendly_path $path $bucket.Source = friendly_path $path
$bucket.Updated = (Get-Item "$path\bucket" -ErrorAction SilentlyContinue).LastWriteTime $bucket.Updated = (Get-Item "$path\bucket").LastWriteTime
} }
$bucket.Manifests = Get-ChildItem "$path\bucket" -Force -Recurse -ErrorAction SilentlyContinue | $bucket.Manifests = Get-ChildItem "$path\bucket" -Force -Recurse -ErrorAction SilentlyContinue |
Measure-Object | Select-Object -ExpandProperty Count Measure-Object | Select-Object -ExpandProperty Count
@@ -154,17 +146,8 @@ function add_bucket($name, $repo) {
} }
ensure $bucketsdir | Out-Null ensure $bucketsdir | Out-Null
$dir = ensure $dir $dir = ensure $dir
$out = Invoke-Git -ArgumentList @('clone', $repo, $dir, '-q') Invoke-Git -ArgumentList @('clone', $repo, $dir, '-q')
if ($LASTEXITCODE -ne 0) {
error "Failed to clone '$repo' to '$dir'.`n`nError given:`n$out`n`nPlease check the repository URL or network connection and try again."
Remove-Item $dir -Recurse -Force -ErrorAction SilentlyContinue
return 1
}
Write-Host 'OK' Write-Host 'OK'
if (get_config USE_SQLITE_CACHE) {
info 'Updating cache...'
Set-ScoopDB -Path (Get-ChildItem (Find-BucketDirectory $name) -Filter '*.json' -Recurse).FullName
}
success "The $name bucket was added successfully." success "The $name bucket was added successfully."
return 0 return 0
} }
@@ -177,11 +160,6 @@ function rm_bucket($name) {
} }
Remove-Item $dir -Recurse -Force -ErrorAction Stop Remove-Item $dir -Recurse -Force -ErrorAction Stop
if (get_config USE_SQLITE_CACHE) {
info 'Updating cache...'
Remove-ScoopDBItem -Bucket $name
}
success "The $name bucket was removed successfully."
return 0 return 0
} }

View File

@@ -1,7 +1,3 @@
# Description: Functions for managing commands and aliases.
## Functions for commands
function command_files { function command_files {
(Get-ChildItem "$PSScriptRoot\..\libexec") + (Get-ChildItem "$scoopdir\shims") | (Get-ChildItem "$PSScriptRoot\..\libexec") + (Get-ChildItem "$scoopdir\shims") |
Where-Object 'scoop-.*?\.ps1$' -Property Name -Match Where-Object 'scoop-.*?\.ps1$' -Property Name -Match
@@ -23,10 +19,11 @@ function command_path($cmd) {
# get path from shim # get path from shim
$shim_path = "$scoopdir\shims\scoop-$cmd.ps1" $shim_path = "$scoopdir\shims\scoop-$cmd.ps1"
$line = ((Get-Content $shim_path) | Where-Object { $_.startswith('$path') }) $line = ((Get-Content $shim_path) | Where-Object { $_.startswith('$path') })
if ($line) { if($line) {
Invoke-Command ([scriptblock]::Create($line)) -NoNewScope Invoke-Command ([scriptblock]::Create($line)) -NoNewScope
$cmd_path = $path $cmd_path = $path
} else { $cmd_path = $shim_path } }
else { $cmd_path = $shim_path }
} }
$cmd_path $cmd_path
@@ -37,92 +34,3 @@ function exec($cmd, $arguments) {
& $cmd_path @arguments & $cmd_path @arguments
} }
## Functions for aliases
function add_alias {
param(
[ValidateNotNullOrEmpty()]
[string]$name,
[ValidateNotNullOrEmpty()]
[string]$command,
[string]$description
)
$aliases = get_config ALIAS ([PSCustomObject]@{})
if ($aliases.$name) {
abort "Alias '$name' already exists."
}
$alias_script_name = "scoop-$name"
$shimdir = shimdir $false
if (Test-Path "$shimdir\$alias_script_name.ps1") {
abort "File '$alias_script_name.ps1' already exists in shims directory."
}
$script = @(
"# Summary: $description",
"$command"
) -join "`n"
try {
$script | Out-UTF8File "$shimdir\$alias_script_name.ps1"
} catch {
abort $_.Exception
}
# Add the new alias to the config.
$aliases | Add-Member -MemberType NoteProperty -Name $name -Value $alias_script_name
set_config ALIAS $aliases | Out-Null
}
function rm_alias {
param(
[ValidateNotNullOrEmpty()]
[string]$name
)
$aliases = get_config ALIAS ([PSCustomObject]@{})
if (!$aliases.$name) {
abort "Alias '$name' doesn't exist."
}
info "Removing alias '$name'..."
if (Test-Path "$(shimdir $false)\scoop-$name.ps1") {
Remove-Item "$(shimdir $false)\scoop-$name.ps1"
}
$aliases.PSObject.Properties.Remove($name)
set_config ALIAS $aliases | Out-Null
}
function list_aliases {
param(
[bool]$verbose
)
$aliases = get_config ALIAS ([PSCustomObject]@{})
$alias_info = $aliases.PSObject.Properties.Name | Where-Object { $_ } | ForEach-Object {
# Mark the alias as <BROKEN>, if the alias script file does NOT exist.
if (!(Test-Path "$(shimdir $false)\scoop-$_.ps1")) {
[PSCustomObject]@{
Name = $_
Command = '<BROKEN>'
}
return
}
$content = Get-Content (command_path $_)
[PSCustomObject]@{
Name = $_
Command = ($content | Select-Object -Skip 1).Trim()
Summary = (summary $content).Trim()
}
}
if (!$alias_info) {
info 'No alias found.'
return
}
$alias_info = $alias_info | Sort-Object Name
$properties = @('Name', 'Command')
if ($verbose) {
$properties += 'Summary'
}
$alias_info | Select-Object $properties
}

View File

@@ -1,51 +1,3 @@
function Get-PESubsystem($filePath) {
try {
$fileStream = [System.IO.FileStream]::new($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
$binaryReader = [System.IO.BinaryReader]::new($fileStream)
$fileStream.Seek(0x3C, [System.IO.SeekOrigin]::Begin) | Out-Null
$peOffset = $binaryReader.ReadInt32()
$fileStream.Seek($peOffset, [System.IO.SeekOrigin]::Begin) | Out-Null
$fileHeaderOffset = $fileStream.Position
$fileStream.Seek(18, [System.IO.SeekOrigin]::Current) | Out-Null
$fileStream.Seek($fileHeaderOffset + 0x5C, [System.IO.SeekOrigin]::Begin) | Out-Null
return $binaryReader.ReadInt16()
} catch {
return -1
} finally {
$binaryReader.Close()
$fileStream.Close()
}
}
function Set-PESubsystem($filePath, $targetSubsystem) {
try {
$fileStream = [System.IO.FileStream]::new($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite)
$binaryReader = [System.IO.BinaryReader]::new($fileStream)
$binaryWriter = [System.IO.BinaryWriter]::new($fileStream)
$fileStream.Seek(0x3C, [System.IO.SeekOrigin]::Begin) | Out-Null
$peOffset = $binaryReader.ReadInt32()
$fileStream.Seek($peOffset, [System.IO.SeekOrigin]::Begin) | Out-Null
$fileHeaderOffset = $fileStream.Position
$fileStream.Seek(18, [System.IO.SeekOrigin]::Current) | Out-Null
$fileStream.Seek($fileHeaderOffset + 0x5C, [System.IO.SeekOrigin]::Begin) | Out-Null
$binaryWriter.Write([System.Int16] $targetSubsystem)
} catch {
return $false
} finally {
$binaryReader.Close()
$fileStream.Close()
}
return $true
}
function Optimize-SecurityProtocol { function Optimize-SecurityProtocol {
# .NET Framework 4.7+ has a default security protocol called 'SystemDefault', # .NET Framework 4.7+ has a default security protocol called 'SystemDefault',
# which allows the operating system to choose the best protocol to use. # which allows the operating system to choose the best protocol to use.
@@ -63,6 +15,18 @@ function Optimize-SecurityProtocol {
} }
} }
function Get-Encoding($wc) {
if ($null -ne $wc.ResponseHeaders -and $wc.ResponseHeaders['Content-Type'] -match 'charset=([^;]*)') {
return [System.Text.Encoding]::GetEncoding($Matches[1])
} else {
return [System.Text.Encoding]::GetEncoding('utf-8')
}
}
function Get-UserAgent() {
return "Scoop/1.0 (+http://scoop.sh/) PowerShell/$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor) (Windows NT $([System.Environment]::OSVersion.Version.Major).$([System.Environment]::OSVersion.Version.Minor); $(if(${env:ProgramFiles(Arm)}){'ARM64; '}elseif($env:PROCESSOR_ARCHITECTURE -eq 'AMD64'){'Win64; x64; '})$(if($env:PROCESSOR_ARCHITEW6432 -in 'AMD64','ARM64'){'WOW64; '})$PSEdition)"
}
function Show-DeprecatedWarning { function Show-DeprecatedWarning {
<# <#
.SYNOPSIS .SYNOPSIS
@@ -120,9 +84,6 @@ function set_config {
$value = [System.Convert]::ToBoolean($value) $value = [System.Convert]::ToBoolean($value)
} }
# Initialize config's change
Complete-ConfigChange -Name $name -Value $value
if ($null -eq $scoopConfig.$name) { if ($null -eq $scoopConfig.$name) {
$scoopConfig | Add-Member -MemberType NoteProperty -Name $name -Value $value $scoopConfig | Add-Member -MemberType NoteProperty -Name $name -Value $value
} else { } else {
@@ -138,81 +99,32 @@ function set_config {
return $scoopConfig return $scoopConfig
} }
function Complete-ConfigChange { function setup_proxy() {
[CmdletBinding()] # note: '@' and ':' in password must be escaped, e.g. 'p@ssword' -> p\@ssword'
param ( $proxy = get_config PROXY
[Parameter(Mandatory, Position = 0)] if(!$proxy) {
[string]
$Name,
[Parameter(Mandatory, Position = 1)]
[AllowEmptyString()]
[string]
$Value
)
if ($Name -eq 'use_isolated_path') {
$oldValue = get_config USE_ISOLATED_PATH
if ($Value -eq $oldValue) {
return return
} else {
$currPathEnvVar = $scoopPathEnvVar
}
. "$PSScriptRoot\..\lib\system.ps1"
if ($Value -eq $false -or $Value -eq '') {
info 'Turn off Scoop isolated path... This may take a while, please wait.'
$movedPath = Get-EnvVar -Name $currPathEnvVar
if ($movedPath) {
Add-Path -Path $movedPath -Quiet
Remove-Path -Path ('%' + $currPathEnvVar + '%') -Quiet
Set-EnvVar -Name $currPathEnvVar -Quiet
}
if (is_admin) {
$movedPath = Get-EnvVar -Name $currPathEnvVar -Global
if ($movedPath) {
Add-Path -Path $movedPath -Global -Quiet
Remove-Path -Path ('%' + $currPathEnvVar + '%') -Global -Quiet
Set-EnvVar -Name $currPathEnvVar -Global -Quiet
}
}
} else {
$newPathEnvVar = if ($Value -eq $true) {
'SCOOP_PATH'
} else {
$Value.ToUpperInvariant()
}
info "Turn on Scoop isolated path ('$newPathEnvVar')... This may take a while, please wait."
$movedPath = Remove-Path -Path "$scoopdir\apps\*" -TargetEnvVar $currPathEnvVar -Quiet -PassThru
if ($movedPath) {
Add-Path -Path $movedPath -TargetEnvVar $newPathEnvVar -Quiet
Add-Path -Path ('%' + $newPathEnvVar + '%') -Quiet
if ($currPathEnvVar -ne 'PATH') {
Remove-Path -Path ('%' + $currPathEnvVar + '%') -Quiet
Set-EnvVar -Name $currPathEnvVar -Quiet
}
}
if (is_admin) {
$movedPath = Remove-Path -Path "$globaldir\apps\*" -TargetEnvVar $currPathEnvVar -Global -Quiet -PassThru
if ($movedPath) {
Add-Path -Path $movedPath -TargetEnvVar $newPathEnvVar -Global -Quiet
Add-Path -Path ('%' + $newPathEnvVar + '%') -Global -Quiet
if ($currPathEnvVar -ne 'PATH') {
Remove-Path -Path ('%' + $currPathEnvVar + '%') -Global -Quiet
Set-EnvVar -Name $currPathEnvVar -Global -Quiet
}
}
}
} }
try {
$credentials, $address = $proxy -split '(?<!\\)@'
if(!$address) {
$address, $credentials = $credentials, $null # no credentials supplied
} }
if ($Name -eq 'use_sqlite_cache' -and $Value -eq $true) { if($address -eq 'none') {
if ((Get-DefaultArchitecture) -eq 'arm64') { [net.webrequest]::defaultwebproxy = $null
abort 'SQLite cache is not supported on ARM64 platform.' } elseif($address -ne 'default') {
[net.webrequest]::defaultwebproxy = new-object net.webproxy "http://$address"
} }
. "$PSScriptRoot\..\lib\database.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1" if($credentials -eq 'currentuser') {
info 'Initializing SQLite cache in progress... This may take a while, please wait.' [net.webrequest]::defaultwebproxy.credentials = [net.credentialcache]::defaultcredentials
Set-ScoopDB } elseif($credentials) {
$username, $password = $credentials -split '(?<!\\):' | ForEach-Object { $_ -replace '\\([@:])','$1' }
[net.webrequest]::defaultwebproxy.credentials = new-object net.networkcredential($username, $password)
}
} catch {
warn "Failed to use proxy '$proxy': $($_.exception.message)"
} }
} }
@@ -233,31 +145,41 @@ function Invoke-Git {
$proxy = get_config PROXY $proxy = get_config PROXY
$git = Get-HelperPath -Helper Git $git = Get-HelperPath -Helper Git
$arguments = $ArgumentList -join ' '
$cmd = "`"$git`" $arguments"
if ($WorkingDirectory) { if ($WorkingDirectory) {
$ArgumentList = @('-C', $WorkingDirectory) + $ArgumentList $cmd = "`"$git`" -C `"$WorkingDirectory`" $arguments"
} }
$sb = [scriptblock]::Create("& $cmd")
if([String]::IsNullOrEmpty($proxy) -or $proxy -eq 'none') { if([String]::IsNullOrEmpty($proxy) -or $proxy -eq 'none') {
return & $git @ArgumentList return Invoke-Command $sb
} }
if($ArgumentList -Match '\b(clone|checkout|pull|fetch|ls-remote)\b') { if($arguments -Match '\b(clone|checkout|pull|fetch|ls-remote)\b') {
$j = Start-Job -ScriptBlock { $old_https = $env:HTTPS_PROXY
$old_http = $env:HTTP_PROXY
try {
# convert proxy setting for git # convert proxy setting for git
$proxy = $using:proxy if ($proxy.StartsWith('currentuser@')) {
if ($proxy -and $proxy.StartsWith('currentuser@')) {
$proxy = $proxy.Replace('currentuser@', ':@') $proxy = $proxy.Replace('currentuser@', ':@')
} }
$env:HTTPS_PROXY = $proxy $env:HTTPS_PROXY = $proxy
$env:HTTP_PROXY = $proxy $env:HTTP_PROXY = $proxy
& $using:git @using:ArgumentList return Invoke-Command $sb
}
catch {
error $_
return
}
finally {
$env:HTTPS_PROXY = $old_https
$env:HTTP_PROXY = $old_http
} }
$o = $j | Receive-Job -Wait -AutoRemoveJob
return $o
} }
return & $git @ArgumentList return Invoke-Command $sb
} }
function Invoke-GitLog { function Invoke-GitLog {
@@ -276,13 +198,17 @@ function Invoke-GitLog {
} }
$Name = "%Cgreen$($Name.PadRight(12, ' ').Substring(0, 12))%Creset " $Name = "%Cgreen$($Name.PadRight(12, ' ').Substring(0, 12))%Creset "
} }
Invoke-Git -Path $Path -ArgumentList @('--no-pager', 'log', '--color', '--no-decorate', "--grep='^(chore)'", '--invert-grep', '--abbrev=12', "--format=tformat: * %C(yellow)%h%Creset %<|(72,trunc)%s $Name%C(cyan)%cr%Creset", "$CommitHash..HEAD") Invoke-Git -Path $Path -ArgumentList @('--no-pager', 'log', '--color', '--no-decorate', "--grep='^(chore)'", '--invert-grep', '--abbrev=12', "--format='tformat: * %C(yellow)%h%Creset %<|(72,trunc)%s $Name%C(cyan)%cr%Creset'", "$CommitHash..HEAD")
} }
} }
# helper functions # helper functions
function coalesce($a, $b) { if($a) { return $a } $b } function coalesce($a, $b) { if($a) { return $a } $b }
function format($str, $hash) {
$hash.keys | ForEach-Object { set-variable $_ $hash[$_] }
$executionContext.invokeCommand.expandString($str)
}
function is_admin { function is_admin {
$admin = [security.principal.windowsbuiltinrole]::administrator $admin = [security.principal.windowsbuiltinrole]::administrator
$id = [security.principal.windowsidentity]::getcurrent() $id = [security.principal.windowsidentity]::getcurrent()
@@ -348,7 +274,6 @@ function filesize($length) {
function basedir($global) { if($global) { return $globaldir } $scoopdir } function basedir($global) { if($global) { return $globaldir } $scoopdir }
function appsdir($global) { "$(basedir $global)\apps" } function appsdir($global) { "$(basedir $global)\apps" }
function shimdir($global) { "$(basedir $global)\shims" } function shimdir($global) { "$(basedir $global)\shims" }
function modulesdir($global) { "$(basedir $global)\modules" }
function appdir($app, $global) { "$(appsdir $global)\$app" } function appdir($app, $global) { "$(appsdir $global)\$app" }
function versiondir($app, $version, $global) { "$(appdir $app $global)\$version" } function versiondir($app, $version, $global) { "$(appdir $app $global)\$version" }
@@ -364,22 +289,7 @@ function currentdir($app, $global) {
function persistdir($app, $global) { "$(basedir $global)\persist\$app" } function persistdir($app, $global) { "$(basedir $global)\persist\$app" }
function usermanifestsdir { "$(basedir)\workspace" } function usermanifestsdir { "$(basedir)\workspace" }
function usermanifest($app) { "$(usermanifestsdir)\$app.json" } function usermanifest($app) { "$(usermanifestsdir)\$app.json" }
function cache_path($app, $version, $url) { function cache_path($app, $version, $url) { "$cachedir\$app#$version#$($url -replace '[^\w\.\-]+', '_')" }
$underscoredUrl = $url -replace '[^\w\.\-]+', '_'
$filePath = Join-Path $cachedir "$app#$version#$underscoredUrl"
# NOTE: Scoop cache files migration. Remove this 6 months after the feature ships.
if (Test-Path $filePath) {
return $filePath
}
$urlStream = [System.IO.MemoryStream]::new([System.Text.Encoding]::UTF8.GetBytes($url))
$sha = (Get-FileHash -Algorithm SHA256 -InputStream $urlStream).Hash.ToLower().Substring(0, 7)
$extension = [System.IO.Path]::GetExtension($url)
$filePath = $filePath -replace "$underscoredUrl", "$sha$extension"
return $filePath
}
# apps # apps
function sanitary_path($path) { return [regex]::replace($path, "[/\\?:*<>|]", "") } function sanitary_path($path) { return [regex]::replace($path, "[/\\?:*<>|]", "") }
@@ -449,7 +359,7 @@ Function Test-CommandAvailable {
} }
Function Test-GitAvailable { Function Test-GitAvailable {
return [Boolean](Get-HelperPath -Helper Git) Return [Boolean](Test-Path (Get-HelperPath -Helper Git) -ErrorAction Ignore)
} }
function Get-HelperPath { function Get-HelperPath {
@@ -457,7 +367,7 @@ function Get-HelperPath {
[OutputType([String])] [OutputType([String])]
param( param(
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[ValidateSet('Git', '7zip', 'Lessmsi', 'Innounp', 'Dark', 'Aria2')] [ValidateSet('Git', '7zip', 'Lessmsi', 'Innounp', 'Dark', 'Aria2', 'Zstd')]
[String] [String]
$Helper $Helper
) )
@@ -467,28 +377,29 @@ function Get-HelperPath {
process { process {
switch ($Helper) { switch ($Helper) {
'Git' { 'Git' {
$internalgit = (Get-AppFilePath 'git' 'mingw64\bin\git.exe'), (Get-AppFilePath 'git' 'mingw32\bin\git.exe') | Where-Object { $_ -ne $null } $internalgit = "$(versiondir 'git' 'current')\mingw64\bin\git.exe"
if ($internalgit) { if (Test-Path $internalgit) {
$HelperPath = $internalgit $HelperPath = $internalgit
} else { } else {
$HelperPath = (Get-Command git -CommandType Application -TotalCount 1 -ErrorAction Ignore).Source $HelperPath = (Get-Command git -ErrorAction Ignore).Source
}
}
'7zip' {
$HelperPath = Get-AppFilePath '7zip' '7z.exe'
if ([String]::IsNullOrEmpty($HelperPath)) {
$HelperPath = Get-AppFilePath '7zip-zstd' '7z.exe'
} }
} }
'7zip' { $HelperPath = Get-AppFilePath '7zip' '7z.exe' }
'Lessmsi' { $HelperPath = Get-AppFilePath 'lessmsi' 'lessmsi.exe' } 'Lessmsi' { $HelperPath = Get-AppFilePath 'lessmsi' 'lessmsi.exe' }
'Innounp' { 'Innounp' { $HelperPath = Get-AppFilePath 'innounp' 'innounp.exe' }
$HelperPath = Get-AppFilePath 'innounp-unicode' 'innounp.exe'
if ([String]::IsNullOrEmpty($HelperPath)) {
$HelperPath = Get-AppFilePath 'innounp' 'innounp.exe'
}
}
'Dark' { 'Dark' {
$HelperPath = Get-AppFilePath 'wixtoolset' 'wix.exe'
if ([String]::IsNullOrEmpty($HelperPath)) {
$HelperPath = Get-AppFilePath 'dark' 'dark.exe' $HelperPath = Get-AppFilePath 'dark' 'dark.exe'
if ([String]::IsNullOrEmpty($HelperPath)) {
$HelperPath = Get-AppFilePath 'wixtoolset' 'dark.exe'
} }
} }
'Aria2' { $HelperPath = Get-AppFilePath 'aria2' 'aria2c.exe' } 'Aria2' { $HelperPath = Get-AppFilePath 'aria2' 'aria2c.exe' }
'Zstd' { $HelperPath = Get-AppFilePath 'zstd' 'zstd.exe' }
} }
return $HelperPath return $HelperPath
@@ -505,8 +416,8 @@ function Get-CommandPath {
) )
begin { begin {
$userShims = shimdir $false $userShims = Convert-Path (shimdir $false)
$globalShims = shimdir $true $globalShims = fullpath (shimdir $true) # don't resolve: may not exist
} }
process { process {
@@ -515,10 +426,7 @@ function Get-CommandPath {
} catch { } catch {
return $null return $null
} }
$commandPath = if ($comm.Path -like "$userShims\scoop-*.ps1") { $commandPath = if ($comm.Path -like "$userShims*" -or $comm.Path -like "$globalShims*") {
# Scoop aliases
$comm.Source
} elseif ($comm.Path -like "$userShims*" -or $comm.Path -like "$globalShims*") {
Get-ShimTarget ($comm.Path -replace '\.exe$', '.shim') Get-ShimTarget ($comm.Path -replace '\.exe$', '.shim')
} elseif ($comm.CommandType -eq 'Application') { } elseif ($comm.CommandType -eq 'Application') {
$comm.Source $comm.Source
@@ -535,7 +443,7 @@ function Test-HelperInstalled {
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[ValidateSet('7zip', 'Lessmsi', 'Innounp', 'Dark', 'Aria2')] [ValidateSet('7zip', 'Lessmsi', 'Innounp', 'Dark', 'Aria2', 'Zstd')]
[String] [String]
$Helper $Helper
) )
@@ -543,6 +451,10 @@ function Test-HelperInstalled {
return ![String]::IsNullOrWhiteSpace((Get-HelperPath -Helper $Helper)) return ![String]::IsNullOrWhiteSpace((Get-HelperPath -Helper $Helper))
} }
function Test-Aria2Enabled {
return (Test-HelperInstalled -Helper Aria2) -and (get_config 'aria2-enabled' $true)
}
function app_status($app, $global) { function app_status($app, $global) {
$status = @{} $status = @{}
$status.installed = installed $app $global $status.installed = installed $app $global
@@ -554,9 +466,6 @@ function app_status($app, $global) {
$status.failed = failed $app $global $status.failed = failed $app $global
$status.hold = ($install_info.hold -eq $true) $status.hold = ($install_info.hold -eq $true)
$deprecated_dir = (Find-BucketDirectory -Name $install_info.bucket -Root) + "\deprecated"
$status.deprecated = (Get-ChildItem $deprecated_dir -Filter "$(sanitary_path $app).json" -Recurse).FullName
$manifest = manifest $app $install_info.bucket $install_info.url $manifest = manifest $app $install_info.bucket $install_info.url
$status.removed = (!$manifest) $status.removed = (!$manifest)
if ($manifest.version) { if ($manifest.version) {
@@ -584,6 +493,7 @@ function app_status($app, $global) {
if ($deps) { if ($deps) {
$status.missing_deps += , $deps $status.missing_deps += , $deps
} }
return $status return $status
} }
@@ -596,53 +506,45 @@ function fname($path) { split-path $path -leaf }
function strip_ext($fname) { $fname -replace '\.[^\.]*$', '' } function strip_ext($fname) { $fname -replace '\.[^\.]*$', '' }
function strip_filename($path) { $path -replace [regex]::escape((fname $path)) } function strip_filename($path) { $path -replace [regex]::escape((fname $path)) }
function strip_fragment($url) { $url -replace (new-object uri $url).fragment } function strip_fragment($url) { $url -replace (new-object uri $url).fragment }
function url_filename($url) {
(split-path $url -leaf).split('?') | Select-Object -First 1
}
# Unlike url_filename which can be tricked by appending a
# URL fragment (e.g. #/dl.7z, useful for coercing a local filename),
# this function extracts the original filename from the URL.
function url_remote_filename($url) {
$uri = (New-Object URI $url)
$basename = Split-Path $uri.PathAndQuery -Leaf
If ($basename -match ".*[?=]+([\w._-]+)") {
$basename = $matches[1]
}
If (($basename -notlike "*.*") -or ($basename -match "^[v.\d]+$")) {
$basename = Split-Path $uri.AbsolutePath -Leaf
}
If (($basename -notlike "*.*") -and ($uri.Fragment -ne "")) {
$basename = $uri.Fragment.Trim('/', '#')
}
return $basename
}
function ensure($dir) { function ensure($dir) {
if (!(Test-Path -Path $dir)) { if (!(Test-Path -Path $dir)) {
New-Item -Path $dir -ItemType Directory | Out-Null New-Item -Path $dir -ItemType Directory | Out-Null
} }
Convert-Path -Path $dir Convert-Path -Path $dir
} }
function Get-AbsolutePath {
<#
.SYNOPSIS
Get absolute path
.DESCRIPTION
Get absolute path, even if not existed
.PARAMETER Path
Path to manipulate
.OUTPUTS
System.String
Absolute path, may or maynot existed
#>
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]
$Path
)
process {
return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
}
}
function fullpath($path) { function fullpath($path) {
Show-DeprecatedWarning $MyInvocation 'Get-AbsolutePath' # should be ~ rooted
return Get-AbsolutePath -Path $path $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path)
} }
function friendly_path($path) { function friendly_path($path) {
$h = (Get-PSProvider 'FileSystem').Home $h = (Get-PsProvider 'FileSystem').home; if(!$h.endswith('\')) { $h += '\' }
if (!$h.EndsWith('\')) { if($h -eq '\') { return $path }
$h += '\' return "$path" -replace ([regex]::escape($h)), "~\"
}
if ($h -eq '\') {
return $path
} else {
return $path -replace ([Regex]::Escape($h)), '~\'
}
} }
function is_local($path) { function is_local($path) {
($path -notmatch '^https?://') -and (Test-Path $path) ($path -notmatch '^https?://') -and (test-path $path)
} }
# operations # operations
@@ -656,7 +558,8 @@ function Invoke-ExternalCommand {
[CmdletBinding(DefaultParameterSetName = "Default")] [CmdletBinding(DefaultParameterSetName = "Default")]
[OutputType([Boolean])] [OutputType([Boolean])]
param ( param (
[Parameter(Mandatory = $true, Position = 0)] [Parameter(Mandatory = $true,
Position = 0)]
[Alias("Path")] [Alias("Path")]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[String] [String]
@@ -668,9 +571,6 @@ function Invoke-ExternalCommand {
[Parameter(ParameterSetName = "UseShellExecute")] [Parameter(ParameterSetName = "UseShellExecute")]
[Switch] [Switch]
$RunAs, $RunAs,
[Parameter(ParameterSetName = "UseShellExecute")]
[Switch]
$Quiet,
[Alias("Msg")] [Alias("Msg")]
[String] [String]
$Activity, $Activity,
@@ -700,45 +600,30 @@ function Invoke-ExternalCommand {
if ($RunAs) { if ($RunAs) {
$Process.StartInfo.UseShellExecute = $true $Process.StartInfo.UseShellExecute = $true
$Process.StartInfo.Verb = 'RunAs' $Process.StartInfo.Verb = 'RunAs'
} else {
$Process.StartInfo.CreateNoWindow = $true
} }
if ($Quiet) { if ($FilePath -match '^((cmd|cscript|wscript|msiexec)(\.exe)?|.*\.(bat|cmd|js|vbs|wsf))$') {
$Process.StartInfo.UseShellExecute = $true $Process.StartInfo.Arguments = $ArgumentList -join ' '
$Process.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden } elseif ($Process.StartInfo.ArgumentList.Add) {
}
if ($ArgumentList.Length -gt 0) {
# Remove existing double quotes and split arguments
# '(?<=(?<![:\w])[/-]\w+) ' matches a space after a command line switch starting with a slash ('/') or a hyphen ('-')
# The inner item '(?<![:\w])[/-]' matches a slash ('/') or a hyphen ('-') not preceded by a colon (':') or a word character ('\w')
# so that it must be a command line switch, otherwise, it would be a path (e.g. 'C:/Program Files') or other word (e.g. 'some-arg')
# ' (?=[/-])' matches a space followed by a slash ('/') or a hyphen ('-'), i.e. the space before a command line switch
$ArgumentList = $ArgumentList.ForEach({ $_ -replace '"' -split '(?<=(?<![:\w])[/-]\w+) | (?=[/-])' })
# Use legacy argument escaping for commands having non-standard behavior with regard to argument passing.
# `msiexec` requires some args like `TARGETDIR="C:\Program Files"`, which is non-standard, therefore we treat it as a legacy command.
# NSIS installer's '/D' param may not work with the ArgumentList property, so we need to escape arguments manually.
# ref-1: https://learn.microsoft.com/en-us/powershell/scripting/learn/experimental-features?view=powershell-7.4#psnativecommandargumentpassing
# ref-2: https://nsis.sourceforge.io/Docs/Chapter3.html
$LegacyCommand = $FilePath -match '^((cmd|cscript|find|sqlcmd|wscript|msiexec)(\.exe)?|.*\.(bat|cmd|js|vbs|wsf))$' -or
($ArgumentList -match '^/S$|^/D=[A-Z]:[\\/].*$').Length -eq 2
$SupportArgumentList = $Process.StartInfo.PSObject.Properties.Name -contains 'ArgumentList'
if ((-not $LegacyCommand) -and $SupportArgumentList) {
# ArgumentList is supported in PowerShell 6.1 and later (built on .NET Core 2.1+) # ArgumentList is supported in PowerShell 6.1 and later (built on .NET Core 2.1+)
# ref-1: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.argumentlist?view=net-6.0 # ref-1: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.argumentlist?view=net-6.0
# ref-2: https://docs.microsoft.com/en-us/powershell/scripting/whats-new/differences-from-windows-powershell?view=powershell-7.2#net-framework-vs-net-core # ref-2: https://docs.microsoft.com/en-us/powershell/scripting/whats-new/differences-from-windows-powershell?view=powershell-7.2#net-framework-vs-net-core
$ArgumentList.ForEach({ $Process.StartInfo.ArgumentList.Add($_) }) $ArgumentList | ForEach-Object { $Process.StartInfo.ArgumentList.Add($_) }
} else { } else {
# Escape arguments manually in lower versions # escape arguments manually in lower versions, refer to https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85)
$escapedArgs = switch -regex ($ArgumentList) { $escapedArgs = $ArgumentList | ForEach-Object {
# Quote paths starting with a drive letter # escape N consecutive backslash(es), which are followed by a double quote, to 2N consecutive ones
'(?<!/D=)[A-Z]:[\\/].*' { $_ -replace '([A-Z]:[\\/].*)', '"$1"'; continue } $s = $_ -replace '(\\+)"', '$1$1"'
# Do not quote paths if it is NSIS's '/D' argument # escape N consecutive backslash(es), which are at the end of the string, to 2N consecutive ones
'/D=[A-Z]:[\\/].*' { $_; continue } $s = $s -replace '(\\+)$', '$1$1'
# Quote args with spaces # escape double quotes
' ' { "`"$_`""; continue } $s = $s -replace '"', '\"'
default { $_; continue } # quote the argument
"`"$s`""
} }
$Process.StartInfo.Arguments = $escapedArgs -join ' ' $Process.StartInfo.Arguments = $escapedArgs -join ' '
} }
}
try { try {
[void]$Process.Start() [void]$Process.Start()
} catch { } catch {
@@ -780,6 +665,12 @@ function Invoke-ExternalCommand {
return $true return $true
} }
function env($name,$global,$val='__get') {
$target = 'User'; if($global) {$target = 'Machine'}
if($val -eq '__get') { [environment]::getEnvironmentVariable($name,$target) }
else { [environment]::setEnvironmentVariable($name,$val,$target) }
}
function isFileLocked([string]$path) { function isFileLocked([string]$path) {
$file = New-Object System.IO.FileInfo $path $file = New-Object System.IO.FileInfo $path
@@ -861,7 +752,7 @@ function Get-ShimTarget($ShimPath) {
if (!$shimTarget) { if (!$shimTarget) {
$shimTarget = ((Select-String -Path $ShimPath -Pattern '[''"]([^@&]*?)[''"]' -AllMatches).Matches.Groups | Select-Object -Last 1).Value $shimTarget = ((Select-String -Path $ShimPath -Pattern '[''"]([^@&]*?)[''"]' -AllMatches).Matches.Groups | Select-Object -Last 1).Value
} }
$shimTarget | Convert-Path -ErrorAction SilentlyContinue $shimTarget | Convert-Path
} }
} }
@@ -887,16 +778,16 @@ function warn_on_overwrite($shim, $path) {
function shim($path, $global, $name, $arg) { function shim($path, $global, $name, $arg) {
if (!(Test-Path $path)) { abort "Can't shim '$(fname $path)': couldn't find '$path'." } if (!(Test-Path $path)) { abort "Can't shim '$(fname $path)': couldn't find '$path'." }
$abs_shimdir = ensure (shimdir $global) $abs_shimdir = ensure (shimdir $global)
Add-Path -Path $abs_shimdir -Global:$global ensure_in_path $abs_shimdir $global
if (!$name) { $name = strip_ext (fname $path) } if (!$name) { $name = strip_ext (fname $path) }
$shim = "$abs_shimdir\$($name.tolower())" $shim = "$abs_shimdir\$($name.tolower())"
# convert to relative path # convert to relative path
$resolved_path = Convert-Path $path
Push-Location $abs_shimdir Push-Location $abs_shimdir
$relative_path = Resolve-Path -Relative $resolved_path $relative_path = Resolve-Path -Relative $path
Pop-Location Pop-Location
$resolved_path = Convert-Path $path
if ($path -match '\.(exe|com)$') { if ($path -match '\.(exe|com)$') {
# for programs with no awareness of any shell # for programs with no awareness of any shell
@@ -906,12 +797,6 @@ function shim($path, $global, $name, $arg) {
if ($arg) { if ($arg) {
Write-Output "args = $arg" | Out-UTF8File "$shim.shim" -Append Write-Output "args = $arg" | Out-UTF8File "$shim.shim" -Append
} }
$target_subsystem = Get-PESubsystem $resolved_path
if ($target_subsystem -eq 2) { # we only want to make shims GUI
Write-Output "Making $shim.exe a GUI binary."
Set-PESubsystem "$shim.exe" $target_subsystem | Out-Null
}
} elseif ($path -match '\.(bat|cmd)$') { } elseif ($path -match '\.(bat|cmd)$') {
# shim .bat, .cmd so they can be used by programs with no awareness of PSH # shim .bat, .cmd so they can be used by programs with no awareness of PSH
warn_on_overwrite "$shim.cmd" $path warn_on_overwrite "$shim.cmd" $path
@@ -973,21 +858,13 @@ function shim($path, $global, $name, $arg) {
warn_on_overwrite "$shim.cmd" $path warn_on_overwrite "$shim.cmd" $path
@( @(
"@rem $resolved_path", "@rem $resolved_path",
"@pushd $(Split-Path $resolved_path -Parent)", "@java -jar `"$resolved_path`" $arg %*"
"@java -jar `"$resolved_path`" $arg %*",
"@popd"
) -join "`r`n" | Out-UTF8File "$shim.cmd" ) -join "`r`n" | Out-UTF8File "$shim.cmd"
warn_on_overwrite $shim $path warn_on_overwrite $shim $path
@( @(
"#!/bin/sh", "#!/bin/sh",
"# $resolved_path", "# $resolved_path",
"if [ `$WSL_INTEROP ]",
'then',
" cd `$(wslpath -u '$(Split-Path $resolved_path -Parent)')",
'else',
" cd `$(cygpath -u '$(Split-Path $resolved_path -Parent)')",
'fi',
"java.exe -jar `"$resolved_path`" $arg `"$@`"" "java.exe -jar `"$resolved_path`" $arg `"$@`""
) -join "`n" | Out-UTF8File $shim -NoNewLine ) -join "`n" | Out-UTF8File $shim -NoNewLine
} elseif ($path -match '\.py$') { } elseif ($path -match '\.py$') {
@@ -999,53 +876,69 @@ function shim($path, $global, $name, $arg) {
warn_on_overwrite $shim $path warn_on_overwrite $shim $path
@( @(
'#!/bin/sh', "#!/bin/sh",
"# $resolved_path", "# $resolved_path",
"python.exe `"$resolved_path`" $arg `"$@`"" "python.exe `"$resolved_path`" $arg `"$@`""
) -join "`n" | Out-UTF8File $shim -NoNewLine ) -join "`n" | Out-UTF8File $shim -NoNewLine
} else { } else {
warn_on_overwrite "$shim.cmd" $path warn_on_overwrite "$shim.cmd" $path
$quoted_arg = if ($arg.Count -gt 0) { $arg | ForEach-Object { "`"$_`"" } } # find path to Git's bash so that batch scripts can run bash scripts
if (!(Get-CommandPath git)) {
error "Can't shim '$shim': 'git' is needed but not installed."
error "Please install git ('scoop install git') and try again."
exit 1
}
$gitdir = (Get-Item (Get-CommandPath git) -ErrorAction:Stop).Directory.Parent
if ($gitdir.FullName -imatch 'mingw') {
$gitdir = $gitdir.Parent
}
@( @(
"@rem $resolved_path", "@rem $resolved_path",
'@echo off', "@`"$(Join-Path (Join-Path $gitdir.FullName 'bin') 'bash.exe')`" `"$resolved_path`" $arg %*"
'bash -c "command -v wslpath >/dev/null"',
'if %errorlevel% equ 0 (',
" bash `"`$(wslpath -u '$resolved_path')`" $quoted_arg %*",
') else (',
" set args=$quoted_arg %*",
' setlocal enabledelayedexpansion',
' if not "!args!"=="" set args=!args:"=""!',
" bash -c `"`$(cygpath -u '$resolved_path') !args!`"",
')'
) -join "`r`n" | Out-UTF8File "$shim.cmd" ) -join "`r`n" | Out-UTF8File "$shim.cmd"
warn_on_overwrite $shim $path warn_on_overwrite $shim $path
@( @(
'#!/bin/sh', "#!/bin/sh",
"# $resolved_path", "# $resolved_path",
"if [ `$WSL_INTEROP ]", "`"$resolved_path`" $arg `"$@`""
'then',
" `"`$(wslpath -u '$resolved_path')`" $arg `"$@`"",
'else',
" `"`$(cygpath -u '$resolved_path')`" $arg `"$@`"",
'fi'
) -join "`n" | Out-UTF8File $shim -NoNewLine ) -join "`n" | Out-UTF8File $shim -NoNewLine
} }
} }
function get_shim_path() { function get_shim_path() {
$shim_version = get_config SHIM 'kiennq' $shim_path = "$(versiondir 'scoop' 'current')\supporting\shims\kiennq\shim.exe"
$shim_path = switch ($shim_version) { $shim_version = get_config SHIM 'default'
'scoopcs' { "$(versiondir 'scoop' 'current')\supporting\shims\scoopcs\shim.exe" } switch ($shim_version) {
'71' { "$(versiondir 'scoop' 'current')\supporting\shims\71\shim.exe" } '71' { $shim_path = "$(versiondir 'scoop' 'current')\supporting\shims\71\shim.exe"; Break }
'kiennq' { "$(versiondir 'scoop' 'current')\supporting\shims\kiennq\shim.exe" } 'scoopcs' { $shim_path = "$(versiondir 'scoop' 'current')\supporting\shimexe\bin\shim.exe"; Break }
'default' { "$(versiondir 'scoop' 'current')\supporting\shims\scoopcs\shim.exe" } 'kiennq' { Break } # for backward compatibility
'default' { Break }
default { warn "Unknown shim version: '$shim_version'" } default { warn "Unknown shim version: '$shim_version'" }
} }
return $shim_path return $shim_path
} }
function search_in_path($target) {
$path = (env 'PATH' $false) + ";" + (env 'PATH' $true)
foreach($dir in $path.split(';')) {
if(test-path "$dir\$target" -pathType leaf) {
return "$dir\$target"
}
}
}
function ensure_in_path($dir, $global) {
$path = env 'PATH' $global
$dir = fullpath $dir
if($path -notmatch [regex]::escape($dir)) {
write-output "Adding $(friendly_path $dir) to $(if($global){'global'}else{'your'}) path."
env 'PATH' $global "$dir;$path" # for future sessions...
$env:PATH = "$dir;$env:PATH" # for this session
}
}
function Get-DefaultArchitecture { function Get-DefaultArchitecture {
$arch = get_config DEFAULT_ARCHITECTURE $arch = get_config DEFAULT_ARCHITECTURE
$system = if (${env:ProgramFiles(Arm)}) { $system = if (${env:ProgramFiles(Arm)}) {
@@ -1120,6 +1013,45 @@ function Confirm-InstallationStatus {
return , $Installed return , $Installed
} }
function strip_path($orig_path, $dir) {
if($null -eq $orig_path) { $orig_path = '' }
$stripped = [string]::join(';', @( $orig_path.split(';') | Where-Object { $_ -and $_ -ne $dir } ))
return ($stripped -ne $orig_path), $stripped
}
function add_first_in_path($dir, $global) {
$dir = fullpath $dir
# future sessions
$null, $currpath = strip_path (env 'path' $global) $dir
env 'path' $global "$dir;$currpath"
# this session
$null, $env:PATH = strip_path $env:PATH $dir
$env:PATH = "$dir;$env:PATH"
}
function remove_from_path($dir, $global) {
$dir = fullpath $dir
# future sessions
$was_in_path, $newpath = strip_path (env 'path' $global) $dir
if($was_in_path) {
Write-Output "Removing $(friendly_path $dir) from your path."
env 'path' $global $newpath
}
# current session
$was_in_path, $newpath = strip_path $env:PATH $dir
if($was_in_path) { $env:PATH = $newpath }
}
function ensure_robocopy_in_path {
if(!(Test-CommandAvailable robocopy)) {
shim "C:\Windows\System32\Robocopy.exe" $false
}
}
function wraptext($text, $width) { function wraptext($text, $width) {
if(!$width) { $width = $host.ui.rawui.buffersize.width }; if(!$width) { $width = $host.ui.rawui.buffersize.width };
$width -= 1 # be conservative: doesn't seem to print the last char $width -= 1 # be conservative: doesn't seem to print the last char
@@ -1148,7 +1080,7 @@ function applist($apps, $global) {
} }
function parse_app([string]$app) { function parse_app([string]$app) {
if ($app -match '^(?:(?<bucket>[a-zA-Z0-9-_.]+)/)?(?<app>.*\.json|[a-zA-Z0-9-_.]+)(?:@(?<version>.*))?$') { if ($app -match '^(?:(?<bucket>[a-zA-Z0-9-_.]+)/)?(?<app>.*\.json$|[a-zA-Z0-9-_.]+)(?:@(?<version>.*))?$') {
return $Matches['app'], $Matches['bucket'], $Matches['version'] return $Matches['app'], $Matches['bucket'], $Matches['version']
} else { } else {
return $app, $null, $null return $app, $null, $null
@@ -1201,8 +1133,8 @@ function Test-ScoopCoreOnHold() {
} }
function substitute($entity, [Hashtable] $params, [Bool]$regexEscape = $false) { function substitute($entity, [Hashtable] $params, [Bool]$regexEscape = $false) {
if ($null -ne $entity) { $newentity = $entity
$newentity = $entity.PSObject.Copy() if ($null -ne $newentity) {
switch ($entity.GetType().Name) { switch ($entity.GetType().Name) {
'String' { 'String' {
$params.GetEnumerator() | ForEach-Object { $params.GetEnumerator() | ForEach-Object {
@@ -1214,7 +1146,7 @@ function substitute($entity, [Hashtable] $params, [Bool]$regexEscape = $false) {
} }
} }
'Object[]' { 'Object[]' {
$newentity = $entity | ForEach-Object { , (substitute $_ $params $regexEscape) } $newentity = $entity | ForEach-Object { ,(substitute $_ $params $regexEscape) }
} }
'PSCustomObject' { 'PSCustomObject' {
$newentity.PSObject.Properties | ForEach-Object { $_.Value = substitute $_.Value $params $regexEscape } $newentity.PSObject.Properties | ForEach-Object { $_.Value = substitute $_.Value $params $regexEscape }
@@ -1224,6 +1156,112 @@ function substitute($entity, [Hashtable] $params, [Bool]$regexEscape = $false) {
return $newentity return $newentity
} }
function format_hash([String] $hash) {
$hash = $hash.toLower()
switch ($hash.Length)
{
32 { $hash = "md5:$hash" } # md5
40 { $hash = "sha1:$hash" } # sha1
64 { $hash = $hash } # sha256
128 { $hash = "sha512:$hash" } # sha512
default { $hash = $null }
}
return $hash
}
function format_hash_aria2([String] $hash) {
$hash = $hash -split ':' | Select-Object -Last 1
switch ($hash.Length)
{
32 { $hash = "md5=$hash" } # md5
40 { $hash = "sha-1=$hash" } # sha1
64 { $hash = "sha-256=$hash" } # sha256
128 { $hash = "sha-512=$hash" } # sha512
default { $hash = $null }
}
return $hash
}
function get_hash([String] $multihash) {
$type, $hash = $multihash -split ':'
if(!$hash) {
# no type specified, assume sha256
$type, $hash = 'sha256', $multihash
}
if(@('md5','sha1','sha256', 'sha512') -notcontains $type) {
return $null, "Hash type '$type' isn't supported."
}
return $type, $hash.ToLower()
}
function Get-GitHubToken {
return $env:SCOOP_GH_TOKEN, (get_config GH_TOKEN) | Where-Object -Property Length -Value 0 -GT | Select-Object -First 1
}
function handle_special_urls($url)
{
# FossHub.com
if ($url -match "^(?:.*fosshub.com\/)(?<name>.*)(?:\/|\?dwl=)(?<filename>.*)$") {
$Body = @{
projectUri = $Matches.name;
fileName = $Matches.filename;
source = 'CF';
isLatestVersion = $true
}
if ((Invoke-RestMethod -Uri $url) -match '"p":"(?<pid>[a-f0-9]{24}).*?"r":"(?<rid>[a-f0-9]{24})') {
$Body.Add("projectId", $Matches.pid)
$Body.Add("releaseId", $Matches.rid)
}
$url = Invoke-RestMethod -Method Post -Uri "https://api.fosshub.com/download/" -ContentType "application/json" -Body (ConvertTo-Json $Body -Compress)
if ($null -eq $url.error) {
$url = $url.data.url
}
}
# Sourceforge.net
if ($url -match "(?:downloads\.)?sourceforge.net\/projects?\/(?<project>[^\/]+)\/(?:files\/)?(?<file>.*?)(?:$|\/download|\?)") {
# Reshapes the URL to avoid redirections
$url = "https://downloads.sourceforge.net/project/$($matches['project'])/$($matches['file'])"
}
# Github.com
if ($url -match 'github.com/(?<owner>[^/]+)/(?<repo>[^/]+)/releases/download/(?<tag>[^/]+)/(?<file>[^/#]+)(?<filename>.*)' -and ($token = Get-GitHubToken)) {
$headers = @{ "Authorization" = "token $token" }
$privateUrl = "https://api.github.com/repos/$($Matches.owner)/$($Matches.repo)"
$assetUrl = "https://api.github.com/repos/$($Matches.owner)/$($Matches.repo)/releases/tags/$($Matches.tag)"
if ((Invoke-RestMethod -Uri $privateUrl -Headers $headers).Private) {
$url = ((Invoke-RestMethod -Uri $assetUrl -Headers $headers).Assets | Where-Object -Property Name -EQ -Value $Matches.file).Url, $Matches.filename -join ''
}
}
return $url
}
function get_magic_bytes($file) {
if(!(Test-Path $file)) {
return ''
}
if((Get-Command Get-Content).parameters.ContainsKey('AsByteStream')) {
# PowerShell Core (6.0+) '-Encoding byte' is replaced by '-AsByteStream'
return Get-Content $file -AsByteStream -TotalCount 8
}
else {
return Get-Content $file -Encoding byte -TotalCount 8
}
}
function get_magic_bytes_pretty($file, $glue = ' ') {
if(!(Test-Path $file)) {
return ''
}
return (get_magic_bytes $file | ForEach-Object { $_.ToString('x2') }) -join $glue
}
function Out-UTF8File { function Out-UTF8File {
param( param(
[Parameter(Mandatory = $True, Position = 0)] [Parameter(Mandatory = $True, Position = 0)]
@@ -1262,50 +1300,53 @@ Optimize-SecurityProtocol
# Load Scoop config # Load Scoop config
$configHome = $env:XDG_CONFIG_HOME, "$env:USERPROFILE\.config" | Select-Object -First 1 $configHome = $env:XDG_CONFIG_HOME, "$env:USERPROFILE\.config" | Select-Object -First 1
$configFile = "$configHome\scoop\config.json" $configFile = "$configHome\scoop\config.json"
# Check if it's the expected install path for scoop: <root>/apps/scoop/current
$coreRoot = Split-Path $PSScriptRoot
$pathExpected = ($coreRoot -replace '\\','/') -like '*apps/scoop/current*'
if ($pathExpected) {
# Portable config is located in root directory:
# .\current\scoop\apps\<root>\config.json <- a reversed path
# Imagine `<root>/apps/scoop/current/` in a reversed format,
# and the directory tree:
#
# ```
# <root>:
# ├─apps
# ├─buckets
# ├─cache
# ├─persist
# ├─shims
# ├─config.json
# ```
$configPortablePath = Get-AbsolutePath "$coreRoot\..\..\..\config.json"
if (Test-Path $configPortablePath) {
$configFile = $configPortablePath
}
}
$scoopConfig = load_cfg $configFile $scoopConfig = load_cfg $configFile
# NOTE Scoop config file migration. Remove this after 2023/6/30
if ($scoopConfig) {
$newConfigNames = @{
'lastUpdate' = 'last_update'
'SCOOP_REPO' = 'scoop_repo'
'SCOOP_BRANCH' = 'scoop_branch'
'7ZIPEXTRACT_USE_EXTERNAL' = 'use_external_7zip'
'MSIEXTRACT_USE_LESSMSI' = 'use_lessmsi'
'NO_JUNCTIONS' = 'no_junction'
'manifest_review' = 'show_manifest'
'rootPath' = 'root_path'
'globalPath' = 'global_path'
'cachePath' = 'cache_path'
}
$newConfigNames.GetEnumerator() | ForEach-Object {
if ($null -ne $scoopConfig.$($_.Key)) {
$value = $scoopConfig.$($_.Key)
$scoopConfig.PSObject.Properties.Remove($_.Key)
$scoopConfig | Add-Member -MemberType NoteProperty -Name $_.Value -Value $value
if ($_.Key -eq 'lastUpdate') {
$scoopConfigChg = $true
}
}
}
if ($scoopConfigChg) { # Only save config file if there was a change
ConvertTo-Json $scoopConfig | Out-UTF8File -FilePath $configFile
}
}
# END NOTE
# Scoop root directory # Scoop root directory
$scoopdir = $env:SCOOP, (get_config ROOT_PATH), "$PSScriptRoot\..\..\..\..", "$([System.Environment]::GetFolderPath('UserProfile'))\scoop" | Where-Object { $_ } | Select-Object -First 1 | Get-AbsolutePath $scoopdir = $env:SCOOP, (get_config ROOT_PATH), "$([System.Environment]::GetFolderPath('UserProfile'))\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1
# Scoop global apps directory # Scoop global apps directory
$globaldir = $env:SCOOP_GLOBAL, (get_config GLOBAL_PATH), "$([System.Environment]::GetFolderPath('CommonApplicationData'))\scoop" | Where-Object { $_ } | Select-Object -First 1 | Get-AbsolutePath $globaldir = $env:SCOOP_GLOBAL, (get_config GLOBAL_PATH), "$([System.Environment]::GetFolderPath('CommonApplicationData'))\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1
# Scoop cache directory # Scoop cache directory
# Note: Setting the SCOOP_CACHE environment variable to use a shared directory # Note: Setting the SCOOP_CACHE environment variable to use a shared directory
# is experimental and untested. There may be concurrency issues when # is experimental and untested. There may be concurrency issues when
# multiple users write and access cached files at the same time. # multiple users write and access cached files at the same time.
# Use at your own risk. # Use at your own risk.
$cachedir = $env:SCOOP_CACHE, (get_config CACHE_PATH), "$scoopdir\cache" | Where-Object { $_ } | Select-Object -First 1 | Get-AbsolutePath $cachedir = $env:SCOOP_CACHE, (get_config CACHE_PATH), "$scoopdir\cache" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1
# Scoop apps' PATH Environment Variable
$scoopPathEnvVar = switch (get_config USE_ISOLATED_PATH) {
{ $_ -is [string] } { $_.ToUpperInvariant() }
$true { 'SCOOP_PATH' }
default { 'PATH' }
}
# OS information # OS information
$WindowsBuild = [System.Environment]::OSVersion.Version.Build $WindowsBuild = [System.Environment]::OSVersion.Version.Build
# Setup proxy globally
setup_proxy

View File

@@ -1,390 +0,0 @@
# Description: Functions for interacting with the Scoop database cache
<#
.SYNOPSIS
Get SQLite .NET driver
.DESCRIPTION
Download and extract the SQLite .NET driver from NuGet.
.PARAMETER Version
System.String
The version of the SQLite .NET driver to download.
.INPUTS
None
.OUTPUTS
System.Boolean
True if the SQLite .NET driver was successfully downloaded and extracted, otherwise false.
#>
function Get-SQLite {
param (
[string]$Version = '1.0.118'
)
# Install SQLite
try {
Write-Host "Downloading SQLite $Version..." -ForegroundColor DarkYellow
$sqlitePkgPath = "$env:TEMP\sqlite.zip"
$sqliteTempPath = "$env:TEMP\sqlite"
$sqlitePath = "$PSScriptRoot\..\supporting\sqlite"
Invoke-WebRequest -Uri "https://api.nuget.org/v3-flatcontainer/stub.system.data.sqlite.core.netframework/$version/stub.system.data.sqlite.core.netframework.$version.nupkg" -OutFile $sqlitePkgPath
Write-Host "Extracting SQLite $Version..." -ForegroundColor DarkYellow -NoNewline
Expand-Archive -Path $sqlitePkgPath -DestinationPath $sqliteTempPath -Force
New-Item -Path $sqlitePath -ItemType Directory -Force | Out-Null
Move-Item -Path "$sqliteTempPath\build\net451\*", "$sqliteTempPath\lib\net451\System.Data.SQLite.dll" -Destination $sqlitePath -Force
Remove-Item -Path $sqlitePkgPath, $sqliteTempPath -Recurse -Force
Write-Host ' Done' -ForegroundColor DarkYellow
return $true
} catch {
return $false
}
}
<#
.SYNOPSIS
Open Scoop SQLite database.
.DESCRIPTION
Open Scoop SQLite database connection and create the necessary tables if not exists.
.INPUTS
None
.OUTPUTS
System.Data.SQLite.SQLiteConnection
The SQLite database connection if **PassThru** is used.
#>
function Open-ScoopDB {
# Load System.Data.SQLite
if (!('System.Data.SQLite.SQLiteConnection' -as [Type])) {
try {
if (!(Test-Path -Path "$PSScriptRoot\..\supporting\sqlite\System.Data.SQLite.dll")) {
Get-SQLite | Out-Null
}
Add-Type -Path "$PSScriptRoot\..\supporting\sqlite\System.Data.SQLite.dll"
} catch {
throw "Scoop's Database cache requires the ADO.NET driver:`n`thttp://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki"
}
}
$dbPath = Join-Path $scoopdir 'scoop.db'
$db = New-Object -TypeName System.Data.SQLite.SQLiteConnection
$db.ConnectionString = "Data Source=$dbPath"
$db.ParseViaFramework = $true # Allow UNC path
$db.Open()
$tableCommand = $db.CreateCommand()
$tableCommand.CommandText = "CREATE TABLE IF NOT EXISTS 'app' (
name TEXT NOT NULL COLLATE NOCASE,
description TEXT NOT NULL,
version TEXT NOT NULL,
bucket VARCHAR NOT NULL,
manifest JSON NOT NULL,
binary TEXT,
shortcut TEXT,
dependency TEXT,
suggest TEXT,
PRIMARY KEY (name, version, bucket)
)"
$tableCommand.CommandType = [System.Data.CommandType]::Text
$tableCommand.ExecuteNonQuery() | Out-Null
$tableCommand.Dispose()
return $db
}
<#
.SYNOPSIS
Set Scoop database item(s).
.DESCRIPTION
Insert or replace item(s) into the Scoop SQLite database.
.PARAMETER InputObject
System.Object[]
The database item(s) to insert or replace.
.INPUTS
System.Object[]
.OUTPUTS
None
#>
function Set-ScoopDBItem {
[CmdletBinding()]
param (
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
[psobject[]]
$InputObject
)
begin {
$db = Open-ScoopDB
$dbTrans = $db.BeginTransaction()
# TODO Support [hashtable]$InputObject
$colName = @($InputObject | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name)
$dbQuery = "INSERT OR REPLACE INTO app ($($colName -join ', ')) VALUES ($('@' + ($colName -join ', @')))"
$dbCommand = $db.CreateCommand()
$dbCommand.CommandText = $dbQuery
$dbCommand.CommandType = [System.Data.CommandType]::Text
}
process {
foreach ($item in $InputObject) {
$item.PSObject.Properties | ForEach-Object {
$dbCommand.Parameters.AddWithValue("@$($_.Name)", $_.Value) | Out-Null
}
$dbCommand.ExecuteNonQuery() | Out-Null
}
}
end {
try {
$dbTrans.Commit()
} catch {
$dbTrans.Rollback()
throw $_
} finally {
$dbCommand.Dispose()
$dbTrans.Dispose()
$db.Dispose()
}
}
}
<#
.SYNOPSIS
Set Scoop app database item(s).
.DESCRIPTION
Insert or replace Scoop app(s) into the database.
.PARAMETER Path
System.String
The path to the bucket.
.PARAMETER CommitHash
System.String
The commit hash to compare with the HEAD.
.INPUTS
None
.OUTPUTS
None
#>
function Set-ScoopDB {
[CmdletBinding()]
param (
[Parameter(Position = 0, ValueFromPipeline)]
[string[]]
$Path
)
begin {
$list = [System.Collections.Generic.List[psobject]]::new()
$arch = Get-DefaultArchitecture
}
process {
if ($Path.Count -eq 0) {
$bucketPath = Get-LocalBucket | ForEach-Object { Find-BucketDirectory $_ }
$Path = (Get-ChildItem $bucketPath -Filter '*.json' -Recurse).FullName
}
$Path | ForEach-Object {
$manifestRaw = [System.IO.File]::ReadAllText($_)
$manifest = ConvertFrom-Json $manifestRaw -ErrorAction SilentlyContinue
if ($null -ne $manifest.version) {
$list.Add([pscustomobject]@{
name = $($_ -replace '.*[\\/]([^\\/]+)\.json$', '$1')
description = if ($manifest.description) { $manifest.description } else { '' }
version = $manifest.version
bucket = $($_ -replace '.*buckets[\\/]([^\\/]+)(?:[\\/].*)', '$1')
manifest = $manifestRaw
binary = $(
$result = @()
@(arch_specific 'bin' $manifest $arch) | ForEach-Object {
if ($_ -is [System.Array]) {
$result += "$($_[1]).$($_[0].Split('.')[-1])"
} else {
$result += $_
}
}
$result -replace '.*?([^\\/]+)?(\.(exe|bat|cmd|ps1|jar|py))$', '$1' -join ' | '
)
shortcut = $(
$result = @()
@(arch_specific 'shortcuts' $manifest $arch) | ForEach-Object {
$result += $_[1]
}
$result -replace '.*?([^\\/]+$)', '$1' -join ' | '
)
dependency = $manifest.depends -join ' | '
suggest = $(
$suggest_output = @()
$manifest.suggest.PSObject.Properties | ForEach-Object {
$suggest_output += $_.Value -join ' | '
}
$suggest_output -join ' | '
)
})
}
}
}
end {
if ($list.Count -ne 0) {
Set-ScoopDBItem $list
}
}
}
<#
.SYNOPSIS
Select Scoop database item(s).
.DESCRIPTION
Select item(s) from the Scoop SQLite database.
The pattern is matched against the name, binaries, and shortcuts columns for apps.
.PARAMETER Pattern
System.String
The pattern to search for. If is an empty string, all items will be returned.
.PARAMETER From
System.String[]
The fields to search from.
.INPUTS
System.String
.OUTPUTS
System.Data.DataTable
The selected database item(s).
#>
function Select-ScoopDBItem {
[CmdletBinding()]
param (
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
[AllowEmptyString()]
[string]
$Pattern,
[Parameter(Mandatory, Position = 1)]
[ValidateNotNullOrEmpty()]
[string[]]
$From
)
begin {
$db = Open-ScoopDB
$dbAdapter = New-Object -TypeName System.Data.SQLite.SQLiteDataAdapter
$result = New-Object System.Data.DataTable
$dbQuery = "SELECT * FROM app WHERE $(($From -join ' LIKE @Pattern OR ') + ' LIKE @Pattern')"
$dbQuery = "SELECT * FROM ($($dbQuery + ' ORDER BY version DESC')) GROUP BY name, bucket"
$dbCommand = $db.CreateCommand()
$dbCommand.CommandText = $dbQuery
$dbCommand.CommandType = [System.Data.CommandType]::Text
$dbAdapter.SelectCommand = $dbCommand
}
process {
$dbCommand.Parameters.AddWithValue('@Pattern', $(if ($Pattern -eq '') { '%' } else { '%' + $Pattern + '%' })) | Out-Null
[void]$dbAdapter.Fill($result)
}
end {
$dbAdapter.Dispose()
$db.Dispose()
return $result
}
}
<#
.SYNOPSIS
Get Scoop database item.
.DESCRIPTION
Get item from the Scoop SQLite database.
.PARAMETER Name
System.String
The name of the item to get.
.PARAMETER Bucket
System.String
The bucket of the item to get.
.PARAMETER Version
System.String
The version of the item to get. If not provided, the latest version will be returned.
.INPUTS
System.String
.OUTPUTS
System.Data.DataTable
The selected database item.
#>
function Get-ScoopDBItem {
[CmdletBinding()]
param (
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
[string]
$Name,
[Parameter(Mandatory, Position = 1)]
[string]
$Bucket,
[Parameter(Position = 2)]
[string]
$Version
)
begin {
$db = Open-ScoopDB
$dbAdapter = New-Object -TypeName System.Data.SQLite.SQLiteDataAdapter
$result = New-Object System.Data.DataTable
$dbQuery = 'SELECT * FROM app WHERE name = @Name AND bucket = @Bucket'
if ($Version) {
$dbQuery += ' AND version = @Version'
} else {
$dbQuery += ' ORDER BY version DESC LIMIT 1'
}
$dbCommand = $db.CreateCommand()
$dbCommand.CommandText = $dbQuery
$dbCommand.CommandType = [System.Data.CommandType]::Text
$dbAdapter.SelectCommand = $dbCommand
}
process {
$dbCommand.Parameters.AddWithValue('@Name', $Name) | Out-Null
$dbCommand.Parameters.AddWithValue('@Bucket', $Bucket) | Out-Null
$dbCommand.Parameters.AddWithValue('@Version', $Version) | Out-Null
[void]$dbAdapter.Fill($result)
}
end {
$dbAdapter.Dispose()
$db.Dispose()
return $result
}
}
<#
.SYNOPSIS
Remove Scoop database item(s).
.DESCRIPTION
Remove item(s) from the Scoop SQLite database.
.PARAMETER Name
System.String
The name of the item to remove.
.PARAMETER Bucket
System.String
The bucket of the item to remove.
.INPUTS
System.String
.OUTPUTS
None
#>
function Remove-ScoopDBItem {
[CmdletBinding()]
param (
[Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string]
$Name,
[Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
[string]
$Bucket
)
begin {
$db = Open-ScoopDB
$dbTrans = $db.BeginTransaction()
$dbQuery = 'DELETE FROM app WHERE bucket = @Bucket'
$dbCommand = $db.CreateCommand()
$dbCommand.CommandText = $dbQuery
$dbCommand.CommandType = [System.Data.CommandType]::Text
}
process {
$dbCommand.Parameters.AddWithValue('@Bucket', $Bucket) | Out-Null
if ($Name) {
$dbCommand.CommandText = $dbQuery + ' AND name = @Name'
$dbCommand.Parameters.AddWithValue('@Name', $Name) | Out-Null
}
$dbCommand.ExecuteNonQuery() | Out-Null
}
end {
try {
$dbTrans.Commit()
} catch {
$dbTrans.Rollback()
throw $_
} finally {
$dbCommand.Dispose()
$dbTrans.Dispose()
$db.Dispose()
}
}
}

View File

@@ -1,67 +1,3 @@
# Description: Functions for decompressing archives or installers
function Invoke-Extraction {
param (
[string]
$Path,
[string[]]
$Name,
[psobject]
$Manifest,
[Alias('Arch', 'Architecture')]
[string]
$ProcessorArchitecture
)
$uri = @(url $Manifest $ProcessorArchitecture)
# 'extract_dir' and 'extract_to' are paired
$extractDir = @(extract_dir $Manifest $ProcessorArchitecture)
$extractTo = @(extract_to $Manifest $ProcessorArchitecture)
$extracted = 0
for ($i = 0; $i -lt $Name.Length; $i++) {
# work out extraction method, if applicable
$extractFn = $null
switch -regex ($Name[$i]) {
'\.zip$' {
if ((Test-HelperInstalled -Helper 7zip) -or ((get_config USE_EXTERNAL_7ZIP) -and (Test-CommandAvailable 7z))) {
$extractFn = 'Expand-7zipArchive'
} else {
$extractFn = 'Expand-ZipArchive'
}
continue
}
'\.msi$' {
$extractFn = 'Expand-MsiArchive'
continue
}
'\.exe$' {
if ($Manifest.innosetup) {
$extractFn = 'Expand-InnoArchive'
}
continue
}
{ Test-7zipRequirement -Uri $_ } {
$extractFn = 'Expand-7zipArchive'
continue
}
}
if ($extractFn) {
$fnArgs = @{
Path = Join-Path $Path $Name[$i]
DestinationPath = Join-Path $Path $extractTo[$extracted]
ExtractDir = $extractDir[$extracted]
}
Write-Host 'Extracting ' -NoNewline
Write-Host $(url_remote_filename $uri[$i]) -ForegroundColor Cyan -NoNewline
Write-Host ' ... ' -NoNewline
& $extractFn @fnArgs -Removal
Write-Host 'done.' -ForegroundColor Green
$extracted++
}
}
}
function Expand-7zipArchive { function Expand-7zipArchive {
[CmdletBinding()] [CmdletBinding()]
param ( param (
@@ -110,6 +46,12 @@ function Expand-7zipArchive {
if (!$Status) { if (!$Status) {
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')" 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) { if ($IsTar) {
# Check for tar # Check for tar
$Status = Invoke-ExternalCommand $7zPath @('l', $Path) -LogPath $LogPath $Status = Invoke-ExternalCommand $7zPath @('l', $Path) -LogPath $LogPath
@@ -121,26 +63,15 @@ function Expand-7zipArchive {
abort "Failed to list files in $Path.`nNot a 7-Zip supported archive file." abort "Failed to list files in $Path.`nNot a 7-Zip supported archive file."
} }
} }
if (!$IsTar -and $ExtractDir) {
movedir "$DestinationPath\$ExtractDir" $DestinationPath | Out-Null
# Remove temporary directory if it is empty
$ExtractDirTopPath = [string] "$DestinationPath\$($ExtractDir -replace '[\\/].*')"
if ((Get-ChildItem -Path $ExtractDirTopPath -Force -ErrorAction Ignore).Count -eq 0) {
Remove-Item -Path $ExtractDirTopPath -Recurse -Force -ErrorAction Ignore
}
}
if (Test-Path $LogPath) {
Remove-Item $LogPath -Force
}
if ($Removal) { if ($Removal) {
# Remove original archive file
if (($Path -replace '.*\.([^\.]*)$', '$1') -eq '001') { if (($Path -replace '.*\.([^\.]*)$', '$1') -eq '001') {
# Remove splitted 7-zip archive parts # Remove splited 7-zip archive parts
Get-ChildItem "$($Path -replace '\.[^\.]*$', '').???" | Remove-Item -Force Get-ChildItem "$($Path -replace '\.[^\.]*$', '').???" | Remove-Item -Force
} elseif (($Path -replace '.*\.part(\d+)\.rar$', '$1')[-1] -eq '1') { } elseif (($Path -replace '.*\.part(\d+)\.rar$', '$1')[-1] -eq '1') {
# Remove splitted RAR archive parts # Remove splitted RAR archive parts
Get-ChildItem "$($Path -replace '\.part(\d+)\.rar$', '').part*.rar" | Remove-Item -Force Get-ChildItem "$($Path -replace '\.part(\d+)\.rar$', '').part*.rar" | Remove-Item -Force
} else { } else {
# Remove original archive file
Remove-Item $Path -Force Remove-Item $Path -Force
} }
} }
@@ -163,9 +94,35 @@ function Expand-ZstdArchive {
[Switch] [Switch]
$Removal $Removal
) )
# TODO: Remove this function after 2024/12/31 $ZstdPath = Get-HelperPath -Helper Zstd
Show-DeprecatedWarning $MyInvocation 'Expand-7zipArchive' $LogPath = Join-Path (Split-Path $Path) 'zstd.log'
Expand-7zipArchive -Path $Path -DestinationPath $DestinationPath -ExtractDir $ExtractDir -Switches $Switches -Removal:$Removal $DestinationPath = $DestinationPath.TrimEnd('\')
ensure $DestinationPath | Out-Null
$ArgList = @('-d', $Path, '--output-dir-flat', $DestinationPath, '-f', '-v')
if ($Switches) {
$ArgList += (-split $Switches)
}
if ($Removal) {
# Remove original archive file
$ArgList += '--rm'
}
$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 (Join-Path $DestinationPath $ExtractDir) $DestinationPath | Out-Null
}
if (Test-Path $LogPath) {
Remove-Item $LogPath -Force
}
if ($IsTar) {
# Check for tar
$TarFile = Join-Path $DestinationPath (strip_ext (fname $Path))
Expand-7zipArchive -Path $TarFile -DestinationPath $DestinationPath -ExtractDir $ExtractDir -Removal
}
} }
function Expand-MsiArchive { function Expand-MsiArchive {
@@ -195,7 +152,7 @@ function Expand-MsiArchive {
$ArgList = @('x', $Path, "$DestinationPath\") $ArgList = @('x', $Path, "$DestinationPath\")
} else { } else {
$MsiPath = 'msiexec.exe' $MsiPath = 'msiexec.exe'
$ArgList = @('/a', $Path, '/qn', "TARGETDIR=$DestinationPath\SourceDir") $ArgList = @('/a', "`"$Path`"", '/qn', "TARGETDIR=`"$DestinationPath\SourceDir`"")
} }
$LogPath = "$(Split-Path $Path)\msi.log" $LogPath = "$(Split-Path $Path)\msi.log"
if ($Switches) { if ($Switches) {
@@ -284,14 +241,8 @@ function Expand-ZipArchive {
$OriDestinationPath = $DestinationPath $OriDestinationPath = $DestinationPath
$DestinationPath = "$DestinationPath\_tmp" $DestinationPath = "$DestinationPath\_tmp"
} }
# Disable progress bar to gain performance
$oldProgressPreference = $ProgressPreference
$global:ProgressPreference = 'SilentlyContinue'
# Compatible with Pscx v3 (https://github.com/Pscx/Pscx) ('Microsoft.PowerShell.Archive' is not needed for Pscx v4) # Compatible with Pscx v3 (https://github.com/Pscx/Pscx) ('Microsoft.PowerShell.Archive' is not needed for Pscx v4)
Microsoft.PowerShell.Archive\Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force Microsoft.PowerShell.Archive\Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force
$global:ProgressPreference = $oldProgressPreference
if ($ExtractDir) { if ($ExtractDir) {
movedir "$DestinationPath\$ExtractDir" $OriDestinationPath | Out-Null movedir "$DestinationPath\$ExtractDir" $OriDestinationPath | Out-Null
Remove-Item $DestinationPath -Recurse -Force Remove-Item $DestinationPath -Recurse -Force
@@ -318,32 +269,14 @@ function Expand-DarkArchive {
$Removal $Removal
) )
$LogPath = "$(Split-Path $Path)\dark.log" $LogPath = "$(Split-Path $Path)\dark.log"
$DarkPath = Get-HelperPath -Helper Dark
if ((Split-Path $DarkPath -Leaf) -eq 'wix.exe') {
$ArgList = @('burn', 'extract', $Path, '-out', $DestinationPath, '-outba', "$DestinationPath\UX")
} else {
$ArgList = @('-nologo', '-x', $DestinationPath, $Path) $ArgList = @('-nologo', '-x', $DestinationPath, $Path)
}
if ($Switches) { if ($Switches) {
$ArgList += (-split $Switches) $ArgList += (-split $Switches)
} }
$Status = Invoke-ExternalCommand $DarkPath $ArgList -LogPath $LogPath $Status = Invoke-ExternalCommand (Get-HelperPath -Helper Dark) $ArgList -LogPath $LogPath
if (!$Status) { if (!$Status) {
abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')" abort "Failed to extract files from $Path.`nLog file:`n $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
} }
if (Test-Path "$DestinationPath\WixAttachedContainer") {
Rename-Item "$DestinationPath\WixAttachedContainer" 'AttachedContainer' -ErrorAction Ignore
} else {
if (Test-Path "$DestinationPath\AttachedContainer\a0") {
$Xml = [xml](Get-Content -Raw "$DestinationPath\UX\manifest.xml" -Encoding utf8)
$Xml.BurnManifest.UX.Payload | ForEach-Object {
Rename-Item "$DestinationPath\UX\$($_.SourcePath)" $_.FilePath -ErrorAction Ignore
}
$Xml.BurnManifest.Payload | ForEach-Object {
Rename-Item "$DestinationPath\AttachedContainer\$($_.SourcePath)" $_.FilePath -ErrorAction Ignore
}
}
}
if (Test-Path $LogPath) { if (Test-Path $LogPath) {
Remove-Item $LogPath -Force Remove-Item $LogPath -Force
} }

View File

@@ -37,9 +37,9 @@ function Get-Dependency {
if (!$manifest) { if (!$manifest) {
if (((Get-LocalBucket) -notcontains $bucket) -and $bucket) { if (((Get-LocalBucket) -notcontains $bucket) -and $bucket) {
warn "Bucket '$bucket' not added. Add it with $(if($bucket -in (known_buckets)) { "'scoop bucket add $bucket' or " })'scoop bucket add $bucket <repo>'." warn "Bucket '$bucket' not installed. Add it with 'scoop bucket add $bucket' or 'scoop bucket add $bucket <repo>'."
} }
abort "Couldn't find manifest for '$AppName'$(if($bucket) { " from '$bucket' bucket" } elseif($url) { " at '$url'" })." abort "Couldn't find manifest for '$AppName'$(if(!$bucket) { '.' } else { " from '$bucket' bucket." })"
} }
$deps = @(Get-InstallationHelper $manifest $Architecture) + @($manifest.depends) | Select-Object -Unique $deps = @(Get-InstallationHelper $manifest $Architecture) + @($manifest.depends) | Select-Object -Unique
@@ -118,8 +118,11 @@ function Get-InstallationHelper {
if ($script -like '*Expand-DarkArchive *') { if ($script -like '*Expand-DarkArchive *') {
$helper += 'dark' $helper += 'dark'
} }
if ((Test-ZstdRequirement -Uri $url) -or ($script -like '*Expand-ZstdArchive *')) {
$helper += 'zstd'
}
if (!$All) { if (!$All) {
'7zip', 'lessmsi', 'innounp', 'dark' | ForEach-Object { '7zip', 'lessmsi', 'innounp', 'dark', 'zstd' | ForEach-Object {
if (Test-HelperInstalled -Helper $_) { if (Test-HelperInstalled -Helper $_) {
$helper = $helper -ne $_ $helper = $helper -ne $_
} }
@@ -141,10 +144,22 @@ function Test-7zipRequirement {
$Uri $Uri
) )
return ($Uri | Where-Object { return ($Uri | Where-Object {
$_ -match '\.(001|7z|bz(ip)?2?|gz|img|iso|lzma|lzh|nupkg|rar|tar|t[abgpx]z2?|t?zst|xz)(\.[^\d.]+)?$' $_ -match '\.((gz)|(tar)|(t[abgpx]z2?)|(lzma)|(bz2?)|(7z)|(001)|(rar)|(iso)|(xz)|(lzh)|(nupkg))(\.[^\d.]+)?$'
}).Count -gt 0 }).Count -gt 0
} }
function Test-ZstdRequirement {
[CmdletBinding()]
[OutputType([Boolean])]
param (
[Parameter(Mandatory = $true)]
[AllowNull()]
[String[]]
$Uri
)
return ($Uri | Where-Object { $_ -match '\.zst$' }).Count -gt 0
}
function Test-LessmsiRequirement { function Test-LessmsiRequirement {
[CmdletBinding()] [CmdletBinding()]
[OutputType([Boolean])] [OutputType([Boolean])]

View File

@@ -1,769 +0,0 @@
# Description: Functions for downloading files
## Meta downloader
function Invoke-ScoopDownload ($app, $version, $manifest, $bucket, $architecture, $dir, $use_cache = $true, $check_hash = $true) {
# we only want to show this warning once
if (!$use_cache) { warn 'Cache is being ignored.' }
# can be multiple urls: if there are, then installer should go first to make 'installer.args' section work
$urls = @(script:url $manifest $architecture)
# can be multiple cookies: they will be used for all HTTP requests.
$cookies = $manifest.cookie
# download first
if (Test-Aria2Enabled) {
Invoke-CachedAria2Download $app $version $manifest $architecture $dir $cookies $use_cache $check_hash
} else {
foreach ($url in $urls) {
$fname = url_filename $url
try {
Invoke-CachedDownload $app $version $url "$dir\$fname" $cookies $use_cache
} catch {
Write-Host -ForegroundColor DarkRed $_
abort "URL $url is not valid"
}
if ($check_hash) {
$manifest_hash = hash_for_url $manifest $url $architecture
$ok, $err = check_hash "$dir\$fname" $manifest_hash $(show_app $app $bucket)
if (!$ok) {
error $err
$cached = cache_path $app $version $url
if (Test-Path $cached) {
# rm cached file
Remove-Item -Force $cached
}
if ($url.Contains('sourceforge.net')) {
Write-Host -ForegroundColor Yellow 'SourceForge.net is known for causing hash validation fails. Please try again before opening a ticket.'
}
abort $(new_issue_msg $app $bucket 'hash check failed')
}
}
}
}
return $urls.ForEach({ url_filename $_ })
}
## [System.Net] downloader
function Invoke-CachedDownload ($app, $version, $url, $to, $cookies = $null, $use_cache = $true) {
$cached = cache_path $app $version $url
if (!(Test-Path $cached) -or !$use_cache) {
ensure $cachedir | Out-Null
Start-Download $url "$cached.download" $cookies
Move-Item "$cached.download" $cached -Force
} else { Write-Host "Loading $(url_remote_filename $url) from cache" }
if (!($null -eq $to)) {
if ($use_cache) {
Copy-Item $cached $to
} else {
Move-Item $cached $to -Force
}
}
}
function Start-Download ($url, $to, $cookies) {
$progress = [console]::isoutputredirected -eq $false -and
$host.name -ne 'Windows PowerShell ISE Host'
try {
$url = handle_special_urls $url
Invoke-Download $url $to $cookies $progress
} catch {
$e = $_.exception
if ($e.Response.StatusCode -eq 'Unauthorized') {
warn 'Token might be misconfigured.'
}
if ($e.innerexception) { $e = $e.innerexception }
throw $e
}
}
function Invoke-Download ($url, $to, $cookies, $progress) {
# download with filesize and progress indicator
$reqUrl = ($url -split '#')[0]
$wreq = [Net.WebRequest]::Create($reqUrl)
if ($wreq -is [Net.HttpWebRequest]) {
$wreq.UserAgent = Get-UserAgent
if (-not ($url -match 'sourceforge\.net' -or $url -match 'portableapps\.com')) {
$wreq.Referer = strip_filename $url
}
if ($url -match 'api\.github\.com/repos') {
$wreq.Accept = 'application/octet-stream'
$wreq.Headers['Authorization'] = "Bearer $(Get-GitHubToken)"
$wreq.Headers['X-GitHub-Api-Version'] = '2022-11-28'
}
if ($cookies) {
$wreq.Headers.Add('Cookie', (cookie_header $cookies))
}
get_config PRIVATE_HOSTS | Where-Object { $_ -ne $null -and $url -match $_.match } | ForEach-Object {
(ConvertFrom-StringData -StringData $_.Headers).GetEnumerator() | ForEach-Object {
$wreq.Headers[$_.Key] = $_.Value
}
}
}
try {
$wres = $wreq.GetResponse()
} catch [System.Net.WebException] {
$exc = $_.Exception
$handledCodes = @(
[System.Net.HttpStatusCode]::MovedPermanently, # HTTP 301
[System.Net.HttpStatusCode]::Found, # HTTP 302
[System.Net.HttpStatusCode]::SeeOther, # HTTP 303
[System.Net.HttpStatusCode]::TemporaryRedirect # HTTP 307
)
# Only handle redirection codes
$redirectRes = $exc.Response
if ($handledCodes -notcontains $redirectRes.StatusCode) {
throw $exc
}
# Get the new location of the file
if ((-not $redirectRes.Headers) -or ($redirectRes.Headers -notcontains 'Location')) {
throw $exc
}
$newUrl = $redirectRes.Headers['Location']
info "Following redirect to $newUrl..."
# Handle manual file rename
if ($url -like '*#/*') {
$null, $postfix = $url -split '#/'
$newUrl = "$newUrl`#/$postfix"
}
Invoke-Download $newUrl $to $cookies $progress
return
}
$total = $wres.ContentLength
if ($total -eq -1 -and $wreq -is [net.ftpwebrequest]) {
$total = ftp_file_size($url)
}
if ($progress -and ($total -gt 0)) {
[console]::CursorVisible = $false
function Trace-DownloadProgress ($read) {
Write-DownloadProgress $read $total $url
}
} else {
Write-Host "Downloading $url ($(filesize $total))..."
function Trace-DownloadProgress {
#no op
}
}
try {
$s = $wres.getresponsestream()
$fs = [io.file]::openwrite($to)
$buffer = New-Object byte[] 2048
$totalRead = 0
$sw = [diagnostics.stopwatch]::StartNew()
Trace-DownloadProgress $totalRead
while (($read = $s.read($buffer, 0, $buffer.length)) -gt 0) {
$fs.write($buffer, 0, $read)
$totalRead += $read
if ($sw.elapsedmilliseconds -gt 100) {
$sw.restart()
Trace-DownloadProgress $totalRead
}
}
$sw.stop()
Trace-DownloadProgress $totalRead
} finally {
if ($progress) {
[console]::CursorVisible = $true
Write-Host
}
if ($fs) {
$fs.close()
}
if ($s) {
$s.close()
}
$wres.close()
}
}
function Format-DownloadProgress ($url, $read, $total, $console) {
$filename = url_remote_filename $url
# calculate current percentage done
$p = [math]::Round($read / $total * 100, 0)
# pre-generate LHS and RHS of progress string
# so we know how much space we have
$left = "$filename ($(filesize $total))"
$right = [string]::Format('{0,3}%', $p)
# calculate remaining width for progress bar
$midwidth = $console.BufferSize.Width - ($left.Length + $right.Length + 8)
# calculate how many characters are completed
$completed = [math]::Abs([math]::Round(($p / 100) * $midwidth, 0) - 1)
# generate dashes to symbolise completed
if ($completed -gt 1) {
$dashes = [string]::Join('', ((1..$completed) | ForEach-Object { '=' }))
}
# this is why we calculate $completed - 1 above
$dashes += switch ($p) {
100 { '=' }
default { '>' }
}
# the remaining characters are filled with spaces
$spaces = switch ($dashes.Length) {
$midwidth { [string]::Empty }
default {
[string]::Join('', ((1..($midwidth - $dashes.Length)) | ForEach-Object { ' ' }))
}
}
"$left [$dashes$spaces] $right"
}
function Write-DownloadProgress ($read, $total, $url) {
$console = $Host.UI.RawUI
$left = $console.CursorPosition.X
$top = $console.CursorPosition.Y
$width = $console.BufferSize.Width
if ($read -eq 0) {
$maxOutputLength = $(Format-DownloadProgress $url 100 $total $console).Length
if (($left + $maxOutputLength) -gt $width) {
# not enough room to print progress on this line
# print on new line
Write-Host
$left = 0
$top = $top + 1
if ($top -gt $console.CursorPosition.Y) { $top = $console.CursorPosition.Y }
}
}
Write-Host $(Format-DownloadProgress $url $read $total $console) -NoNewline
[console]::SetCursorPosition($left, $top)
}
## Aria2 downloader
function Test-Aria2Enabled {
return (Test-HelperInstalled -Helper Aria2) -and (get_config 'aria2-enabled' $true)
}
function aria_exit_code($exitcode) {
$codes = @{
0 = 'All downloads were successful'
1 = 'An unknown error occurred'
2 = 'Timeout'
3 = 'Resource was not found'
4 = 'Aria2 saw the specified number of "resource not found" error. See --max-file-not-found option'
5 = 'Download aborted because download speed was too slow. See --lowest-speed-limit option'
6 = 'Network problem occurred.'
7 = 'There were unfinished downloads. This error is only reported if all finished downloads were successful and there were unfinished downloads in a queue when aria2 exited by pressing Ctrl-C by an user or sending TERM or INT signal'
8 = 'Remote server did not support resume when resume was required to complete download'
9 = 'There was not enough disk space available'
10 = 'Piece length was different from one in .aria2 control file. See --allow-piece-length-change option'
11 = 'Aria2 was downloading same file at that moment'
12 = 'Aria2 was downloading same info hash torrent at that moment'
13 = 'File already existed. See --allow-overwrite option'
14 = 'Renaming file failed. See --auto-file-renaming option'
15 = 'Aria2 could not open existing file'
16 = 'Aria2 could not create new file or truncate existing file'
17 = 'File I/O error occurred'
18 = 'Aria2 could not create directory'
19 = 'Name resolution failed'
20 = 'Aria2 could not parse Metalink document'
21 = 'FTP command failed'
22 = 'HTTP response header was bad or unexpected'
23 = 'Too many redirects occurred'
24 = 'HTTP authorization failed'
25 = 'Aria2 could not parse bencoded file (usually ".torrent" file)'
26 = '".torrent" file was corrupted or missing information that aria2 needed'
27 = 'Magnet URI was bad'
28 = 'Bad/unrecognized option was given or unexpected option argument was given'
29 = 'The remote server was unable to handle the request due to a temporary overloading or maintenance'
30 = 'Aria2 could not parse JSON-RPC request'
31 = 'Reserved. Not used'
32 = 'Checksum validation failed'
}
if ($null -eq $codes[$exitcode]) {
return 'An unknown error occurred'
}
return $codes[$exitcode]
}
function get_filename_from_metalink($file) {
$bytes = get_magic_bytes_pretty $file ''
# check if file starts with '<?xml'
if (!($bytes.StartsWith('3c3f786d6c'))) {
return $null
}
# Add System.Xml for reading metalink files
Add-Type -AssemblyName 'System.Xml'
$xr = [System.Xml.XmlReader]::Create($file)
$filename = $null
try {
$xr.ReadStartElement('metalink')
if ($xr.ReadToFollowing('file') -and $xr.MoveToFirstAttribute()) {
$filename = $xr.Value
}
} catch [System.Xml.XmlException] {
return $null
} finally {
$xr.Close()
}
return $filename
}
function Invoke-CachedAria2Download ($app, $version, $manifest, $architecture, $dir, $cookies = $null, $use_cache = $true, $check_hash = $true) {
$data = @{}
$urls = @(script:url $manifest $architecture)
# aria2 input file
$urlstxt = Join-Path $cachedir "$app.txt"
$urlstxt_content = ''
$download_finished = $true
# aria2 options
$options = @(
"--input-file='$urlstxt'"
"--user-agent='$(Get-UserAgent)'"
'--allow-overwrite=true'
'--auto-file-renaming=false'
"--retry-wait=$(get_config 'aria2-retry-wait' 2)"
"--split=$(get_config 'aria2-split' 5)"
"--max-connection-per-server=$(get_config 'aria2-max-connection-per-server' 5)"
"--min-split-size=$(get_config 'aria2-min-split-size' '5M')"
'--console-log-level=warn'
'--enable-color=false'
'--no-conf=true'
'--follow-metalink=true'
'--metalink-preferred-protocol=https'
'--min-tls-version=TLSv1.2'
"--stop-with-process=$PID"
'--continue'
'--summary-interval=0'
'--auto-save-interval=1'
)
if ($cookies) {
$options += "--header='Cookie: $(cookie_header $cookies)'"
}
$proxy = get_config PROXY
if ($proxy -ne 'none') {
if ([Net.Webrequest]::DefaultWebProxy.Address) {
$options += "--all-proxy='$([Net.Webrequest]::DefaultWebProxy.Address.Authority)'"
}
if ([Net.Webrequest]::DefaultWebProxy.Credentials.UserName) {
$options += "--all-proxy-user='$([Net.Webrequest]::DefaultWebProxy.Credentials.UserName)'"
}
if ([Net.Webrequest]::DefaultWebProxy.Credentials.Password) {
$options += "--all-proxy-passwd='$([Net.Webrequest]::DefaultWebProxy.Credentials.Password)'"
}
}
$more_options = get_config 'aria2-options'
if ($more_options) {
$options += $more_options
}
foreach ($url in $urls) {
$data.$url = @{
'target' = Join-Path $dir (url_filename $url)
'cachename' = fname (cache_path $app $version $url)
'source' = cache_path $app $version $url
}
if ((Test-Path $data.$url.source) -and -not((Test-Path "$($data.$url.source).aria2") -or (Test-Path $urlstxt)) -and $use_cache) {
Write-Host 'Loading ' -NoNewline
Write-Host $(url_remote_filename $url) -ForegroundColor Cyan -NoNewline
Write-Host ' from cache.'
} else {
$download_finished = $false
# create aria2 input file content
try {
$try_url = handle_special_urls $url
} catch {
if ($_.Exception.Response.StatusCode -eq 'Unauthorized') {
warn 'Token might be misconfigured.'
}
}
$urlstxt_content += "$try_url`n"
if (!$url.Contains('sourceforge.net')) {
$urlstxt_content += " referer=$(strip_filename $url)`n"
}
$urlstxt_content += " dir=$cachedir`n"
$urlstxt_content += " out=$($data.$url.cachename)`n"
}
}
if (-not($download_finished)) {
# write aria2 input file
if ($urlstxt_content -ne '') {
ensure $cachedir | Out-Null
# Write aria2 input-file with UTF8NoBOM encoding
$urlstxt_content | Out-UTF8File -FilePath $urlstxt
}
# build aria2 command
$aria2 = "& '$(Get-HelperPath -Helper Aria2)' $($options -join ' ')"
# handle aria2 console output
Write-Host 'Starting download with aria2 ...'
# Set console output encoding to UTF8 for non-ASCII characters printing
$oriConsoleEncoding = [Console]::OutputEncoding
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding
Invoke-Command ([scriptblock]::Create($aria2)) | ForEach-Object {
# Skip blank lines
if ([String]::IsNullOrWhiteSpace($_)) { return }
# Prevent potential overlaping of text when one line is shorter
$len = $Host.UI.RawUI.WindowSize.Width - $_.Length - 20
$blank = if ($len -gt 0) { ' ' * $len } else { '' }
$color = 'Gray'
if ($_.StartsWith('(OK):')) {
$noNewLine = $true
$color = 'Green'
} elseif ($_.StartsWith('[') -and $_.EndsWith(']')) {
$noNewLine = $true
$color = 'Cyan'
} elseif ($_.StartsWith('Download Results:')) {
$noNewLine = $false
}
Write-Host "`rDownload: $_$blank" -ForegroundColor $color -NoNewline:$noNewLine
}
Write-Host ''
if ($lastexitcode -gt 0) {
warn "Download failed! (Error $lastexitcode) $(aria_exit_code $lastexitcode)"
warn $urlstxt_content
warn $aria2
warn $(new_issue_msg $app $bucket "download via aria2 failed")
Write-Host "Fallback to default downloader ..."
try {
foreach ($url in $urls) {
Invoke-CachedDownload $app $version $url "$($data.$url.target)" $cookies $use_cache
}
} catch {
Write-Host $_ -ForegroundColor DarkRed
abort "URL $url is not valid"
}
}
# remove aria2 input file when done
if (Test-Path $urlstxt, "$($data.$url.source).aria2*") {
Remove-Item $urlstxt -Force -ErrorAction SilentlyContinue
Remove-Item "$($data.$url.source).aria2*" -Force -ErrorAction SilentlyContinue
}
# Revert console encoding
[Console]::OutputEncoding = $oriConsoleEncoding
}
foreach ($url in $urls) {
$metalink_filename = get_filename_from_metalink $data.$url.source
if ($metalink_filename) {
Remove-Item $data.$url.source -Force
Rename-Item -Force (Join-Path -Path $cachedir -ChildPath $metalink_filename) $data.$url.source
}
# run hash checks
if ($check_hash) {
$manifest_hash = hash_for_url $manifest $url $architecture
$ok, $err = check_hash $data.$url.source $manifest_hash $(show_app $app $bucket)
if (!$ok) {
error $err
if (Test-Path $data.$url.source) {
# rm cached file
Remove-Item $data.$url.source -Force -ErrorAction SilentlyContinue
Remove-Item "$($data.$url.source).aria2*" -Force -ErrorAction SilentlyContinue
}
if ($url.Contains('sourceforge.net')) {
Write-Host -f yellow 'SourceForge.net is known for causing hash validation fails. Please try again before opening a ticket.'
}
abort $(new_issue_msg $app $bucket 'hash check failed')
}
}
# copy or move file to target location
if (!(Test-Path $data.$url.source) ) {
abort $(new_issue_msg $app $bucket 'cached file not found')
}
if (!($dir -eq $cachedir)) {
if ($use_cache) {
Copy-Item $data.$url.source $data.$url.target
} else {
Move-Item $data.$url.source $data.$url.target -Force
}
}
}
}
## Helper functions
### Downloader parameters
function cookie_header($cookies) {
if (!$cookies) { return }
$vals = $cookies.psobject.properties | ForEach-Object {
"$($_.name)=$($_.value)"
}
[string]::join(';', $vals)
}
function Get-Encoding($wc) {
if ($null -ne $wc.ResponseHeaders -and $wc.ResponseHeaders['Content-Type'] -match 'charset=([^;]*)') {
return [System.Text.Encoding]::GetEncoding($Matches[1])
} else {
return [System.Text.Encoding]::GetEncoding('utf-8')
}
}
function Get-UserAgent() {
return "Scoop/1.0 (+http://scoop.sh/) PowerShell/$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor) (Windows NT $([System.Environment]::OSVersion.Version.Major).$([System.Environment]::OSVersion.Version.Minor); $(if(${env:ProgramFiles(Arm)}){'ARM64; '}elseif($env:PROCESSOR_ARCHITECTURE -eq 'AMD64'){'Win64; x64; '})$(if($env:PROCESSOR_ARCHITEW6432 -in 'AMD64','ARM64'){'WOW64; '})$PSEdition)"
}
function setup_proxy() {
# note: '@' and ':' in password must be escaped, e.g. 'p@ssword' -> p\@ssword'
$proxy = get_config PROXY
if (!$proxy) {
return
}
try {
$credentials, $address = $proxy -split '(?<!\\)@'
if (!$address) {
$address, $credentials = $credentials, $null # no credentials supplied
}
if ($address -eq 'none') {
[net.webrequest]::defaultwebproxy = $null
} elseif ($address -ne 'default') {
[net.webrequest]::defaultwebproxy = New-Object net.webproxy "http://$address"
}
if ($credentials -eq 'currentuser') {
[net.webrequest]::defaultwebproxy.credentials = [net.credentialcache]::defaultcredentials
} elseif ($credentials) {
$username, $password = $credentials -split '(?<!\\):' | ForEach-Object { $_ -replace '\\([@:])', '$1' }
[net.webrequest]::defaultwebproxy.credentials = New-Object net.networkcredential($username, $password)
}
} catch {
warn "Failed to use proxy '$proxy': $($_.exception.message)"
}
}
function Get-GitHubToken {
return $env:SCOOP_GH_TOKEN, (get_config GH_TOKEN) | Where-Object -Property Length -Value 0 -GT | Select-Object -First 1
}
function github_ratelimit_reached {
$api_link = 'https://api.github.com/rate_limit'
$ret = (download_json $api_link).rate.remaining -eq 0
if ($ret) {
Write-Host "GitHub API rate limit reached.`r`nPlease try again later or configure your API token using 'scoop config gh_token <your token>'."
}
$ret
}
### URL handling
function handle_special_urls($url) {
# FossHub.com
if ($url -match '^(?:.*fosshub.com\/)(?<name>.*)(?:\/|\?dwl=)(?<filename>.*)$') {
$Body = @{
projectUri = $Matches.name
fileName = $Matches.filename
source = 'CF'
isLatestVersion = $true
}
if ((Invoke-RestMethod -Uri $url) -match '"p":"(?<pid>[a-f0-9]{24}).*?"r":"(?<rid>[a-f0-9]{24})') {
$Body.Add('projectId', $Matches.pid)
$Body.Add('releaseId', $Matches.rid)
}
$url = Invoke-RestMethod -Method Post -Uri 'https://api.fosshub.com/download/' -ContentType 'application/json' -Body (ConvertTo-Json $Body -Compress)
if ($null -eq $url.error) {
$url = $url.data.url
}
}
# Sourceforge.net
if ($url -match '(?:downloads\.)?sourceforge.net\/projects?\/(?<project>[^\/]+)\/(?:files\/)?(?<file>.*?)(?:$|\/download|\?)') {
# Reshapes the URL to avoid redirections
$url = "https://downloads.sourceforge.net/project/$($matches['project'])/$($matches['file'])"
}
# Github.com
if ($url -match 'github.com/(?<owner>[^/]+)/(?<repo>[^/]+)/releases/download/(?<tag>[^/]+)/(?<file>[^/#]+)(?<filename>.*)' -and ($token = Get-GitHubToken)) {
$headers = @{ 'Authorization' = "token $token" }
$privateUrl = "https://api.github.com/repos/$($Matches.owner)/$($Matches.repo)"
$assetUrl = "https://api.github.com/repos/$($Matches.owner)/$($Matches.repo)/releases/tags/$($Matches.tag)"
if ((Invoke-RestMethod -Uri $privateUrl -Headers $headers).Private) {
$url = ((Invoke-RestMethod -Uri $assetUrl -Headers $headers).Assets | Where-Object -Property Name -EQ -Value $Matches.file).Url, $Matches.filename -join ''
}
}
return $url
}
### Remote file information
function download_json($url) {
$githubtoken = Get-GitHubToken
$authheader = @{}
if ($githubtoken) {
$authheader = @{'Authorization' = "token $githubtoken" }
}
$ProgressPreference = 'SilentlyContinue'
$result = Invoke-WebRequest $url -UseBasicParsing -Headers $authheader | Select-Object -ExpandProperty content | ConvertFrom-Json
$ProgressPreference = 'Continue'
$result
}
function get_magic_bytes($file) {
if (!(Test-Path $file)) {
return ''
}
if ((Get-Command Get-Content).parameters.ContainsKey('AsByteStream')) {
# PowerShell Core (6.0+) '-Encoding byte' is replaced by '-AsByteStream'
return Get-Content $file -AsByteStream -TotalCount 8
} else {
return Get-Content $file -Encoding byte -TotalCount 8
}
}
function get_magic_bytes_pretty($file, $glue = ' ') {
if (!(Test-Path $file)) {
return ''
}
return (get_magic_bytes $file | ForEach-Object { $_.ToString('x2') }) -join $glue
}
Function Get-RemoteFileSize ($Uri) {
$response = Invoke-WebRequest -Uri $Uri -Method HEAD -UseBasicParsing
if (!$response.Headers.StatusCode) {
$response.Headers.'Content-Length' | ForEach-Object { [int]$_ }
}
}
function ftp_file_size($url) {
$request = [net.ftpwebrequest]::create($url)
$request.method = [net.webrequestmethods+ftp]::getfilesize
$request.getresponse().contentlength
}
function url_filename($url) {
(Split-Path $url -Leaf).split('?') | Select-Object -First 1
}
function url_remote_filename($url) {
# Unlike url_filename which can be tricked by appending a
# URL fragment (e.g. #/dl.7z, useful for coercing a local filename),
# this function extracts the original filename from the URL.
$uri = (New-Object URI $url)
$basename = Split-Path $uri.PathAndQuery -Leaf
If ($basename -match '.*[?=]+([\w._-]+)') {
$basename = $matches[1]
}
If (($basename -notlike '*.*') -or ($basename -match '^[v.\d]+$')) {
$basename = Split-Path $uri.AbsolutePath -Leaf
}
If (($basename -notlike '*.*') -and ($uri.Fragment -ne '')) {
$basename = $uri.Fragment.Trim('/', '#')
}
return $basename
}
### Hash-related functions
function hash_for_url($manifest, $url, $arch) {
$hashes = @(hash $manifest $arch) | Where-Object { $_ -ne $null }
if ($hashes.length -eq 0) { return $null }
$urls = @(script:url $manifest $arch)
$index = [array]::IndexOf($urls, $url)
if ($index -eq -1) { abort "Couldn't find hash in manifest for '$url'." }
@($hashes)[$index]
}
function check_hash($file, $hash, $app_name) {
# returns (ok, err)
if (!$hash) {
warn "Warning: No hash in manifest. SHA256 for '$(fname $file)' is:`n $((Get-FileHash -Path $file -Algorithm SHA256).Hash.ToLower())"
return $true, $null
}
Write-Host 'Checking hash of ' -NoNewline
Write-Host $(url_remote_filename $url) -ForegroundColor Cyan -NoNewline
Write-Host ' ... ' -NoNewline
$algorithm, $expected = get_hash $hash
if ($null -eq $algorithm) {
return $false, "Hash type '$algorithm' isn't supported."
}
$actual = (Get-FileHash -Path $file -Algorithm $algorithm).Hash.ToLower()
$expected = $expected.ToLower()
if ($actual -ne $expected) {
$msg = "Hash check failed!`n"
$msg += "App: $app_name`n"
$msg += "URL: $url`n"
if (Test-Path $file) {
$msg += "First bytes: $((get_magic_bytes_pretty $file ' ').ToUpper())`n"
}
if ($expected -or $actual) {
$msg += "Expected: $expected`n"
$msg += "Actual: $actual"
}
return $false, $msg
}
Write-Host 'ok.' -f Green
return $true, $null
}
function get_hash([String] $multihash) {
$type, $hash = $multihash -split ':'
if (!$hash) {
# no type specified, assume sha256
$type, $hash = 'sha256', $multihash
}
if (@('md5', 'sha1', 'sha256', 'sha512') -notcontains $type) {
return $null, "Hash type '$type' isn't supported."
}
return $type, $hash.ToLower()
}
# Setup proxy globally
setup_proxy

View File

@@ -13,7 +13,7 @@
# following arguments are treated as non-option arguments, even if # following arguments are treated as non-option arguments, even if
# they begin with a hyphen. The "--" itself will not be included in # they begin with a hyphen. The "--" itself will not be included in
# the returned $opts. (POSIX-compatible) # the returned $opts. (POSIX-compatible)
function getopt([String[]]$argv, [String]$shortopts, [String[]]$longopts) { function getopt($argv, $shortopts, $longopts) {
$opts = @{}; $rem = @() $opts = @{}; $rem = @()
function err($msg) { function err($msg) {
@@ -24,6 +24,10 @@ function getopt([String[]]$argv, [String]$shortopts, [String[]]$longopts) {
return [Regex]::Escape($str) return [Regex]::Escape($str)
} }
# ensure these are arrays
$argv = @($argv -split ' ')
$longopts = @($longopts)
for ($i = 0; $i -lt $argv.Length; $i++) { for ($i = 0; $i -lt $argv.Length; $i++) {
$arg = $argv[$i] $arg = $argv[$i]
if ($null -eq $arg) { continue } if ($null -eq $arg) { continue }
@@ -77,5 +81,6 @@ function getopt([String[]]$argv, [String]$shortopts, [String[]]$longopts) {
$rem += $arg $rem += $arg
} }
} }
$opts, $rem $opts, $rem
} }

File diff suppressed because it is too large Load Diff

View File

@@ -110,13 +110,13 @@ function json_path([String] $json, [String] $jsonpath, [Hashtable] $substitution
# Return versions in reverse order # Return versions in reverse order
$result = [System.Linq.Enumerable]::Reverse($result) $result = [System.Linq.Enumerable]::Reverse($result)
} }
if ([System.Linq.Enumerable]::Count($result) -eq 1 -or $single) { if ($single) {
# Extract First value # Extract First value
$result = [System.Linq.Enumerable]::First($result) $result = [System.Linq.Enumerable]::First($result)
# Convert first value to string # Convert first value to string
$result = $result.ToString() $result = $result.ToString()
} else { } else {
$result = [Newtonsoft.Json.JsonConvert]::SerializeObject($result) $result = "$([String]::Join('\n', $result))"
} }
return $result return $result
} catch [Exception] { } catch [Exception] {

View File

@@ -7,7 +7,7 @@ function parse_json($path) {
try { try {
Get-Content $path -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop Get-Content $path -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop
} catch { } catch {
warn "Error parsing JSON at '$path'." warn "Error parsing JSON at $path."
} }
} }
@@ -23,11 +23,11 @@ function url_manifest($url) {
} catch { } catch {
throw throw
} }
if (!$str) { return $null } if(!$str) { return $null }
try { try {
$str | ConvertFrom-Json -ErrorAction Stop $str | ConvertFrom-Json -ErrorAction Stop
} catch { } catch {
warn "Error parsing JSON at '$url'." warn "Error parsing JSON at $url."
} }
} }
@@ -39,75 +39,32 @@ function Get-Manifest($app) {
$url = $app $url = $app
$app = appname_from_url $url $app = appname_from_url $url
$manifest = url_manifest $url $manifest = url_manifest $url
} else {
# Check if the manifest is already installed
if (installed $app) {
$global = installed $app $true
$ver = Select-CurrentVersion -AppName $app -Global:$global
if (!$ver) {
$app, $bucket, $ver = parse_app $app
$ver = Select-CurrentVersion -AppName $app -Global:$global
}
$install_info_path = "$(versiondir $app $ver $global)\install.json"
if (Test-Path $install_info_path) {
$install_info = parse_json $install_info_path
$bucket = $install_info.bucket
if (!$bucket) {
$url = $install_info.url
if ($url -match '^(ht|f)tps?://|\\\\') {
$manifest = url_manifest $url
}
if (!$manifest) {
if (Test-Path $url) {
$manifest = parse_json $url
} else {
# Fallback to installed manifest
$manifest = installed_manifest $app $ver $global
}
}
} else {
$manifest = manifest $app $bucket
if (!$manifest) {
$deprecated_dir = (Find-BucketDirectory -Name $bucket -Root) + '\deprecated'
$manifest = parse_json (Get-ChildItem $deprecated_dir -Filter "$(sanitary_path $app).json" -Recurse).FullName
}
}
}
} else { } else {
$app, $bucket, $version = parse_app $app $app, $bucket, $version = parse_app $app
if ($bucket) { if ($bucket) {
$manifest = manifest $app $bucket $manifest = manifest $app $bucket
} else { } else {
$matched_buckets = @() foreach ($bucket in Get-LocalBucket) {
foreach ($tekcub in Get-LocalBucket) { $manifest = manifest $app $bucket
$current_manifest = manifest $app $tekcub if ($manifest) {
if (!$manifest -and $current_manifest) { break
$manifest = $current_manifest
$bucket = $tekcub
}
if ($current_manifest) {
$matched_buckets += $tekcub
} }
} }
} }
if (!$manifest) { if (!$manifest) {
# couldn't find app in buckets: check if it's a local path # couldn't find app in buckets: check if it's a local path
if (Test-Path $app) { $appPath = $app
$url = Convert-Path $app $bucket = $null
if (!$appPath.EndsWith('.json')) {
$appPath += '.json'
}
if (Test-Path $appPath) {
$url = Convert-Path $appPath
$app = appname_from_url $url $app = appname_from_url $url
$manifest = parse_json $url $manifest = url_manifest $url
} else {
if (($app -match '\\/') -or $app.EndsWith('.json')) { $url = $app }
$app = appname_from_url $app
} }
} }
} }
}
if ($matched_buckets.Length -gt 1) {
warn "Multiple buckets contain manifest '$app', the current selection is '$bucket/$app'."
}
return $app, $manifest, $bucket, $url return $app, $manifest, $bucket, $url
} }
@@ -181,26 +138,16 @@ function generate_user_manifest($app, $bucket, $version) {
warn "Given version ($version) does not match manifest ($($manifest.version))" warn "Given version ($version) does not match manifest ($($manifest.version))"
warn "Attempting to generate manifest for '$app' ($version)" warn "Attempting to generate manifest for '$app' ($version)"
ensure (usermanifestsdir) | Out-Null
$manifest_path = "$(usermanifestsdir)\$app.json"
if (get_config USE_SQLITE_CACHE) {
$cached_manifest = (Get-ScoopDBItem -Name $app -Bucket $bucket -Version $version).manifest
if ($cached_manifest) {
$cached_manifest | Out-UTF8File $manifest_path
return $manifest_path
}
}
if (!($manifest.autoupdate)) { if (!($manifest.autoupdate)) {
abort "'$app' does not have autoupdate capability`r`ncouldn't find manifest for '$app@$version'" abort "'$app' does not have autoupdate capability`r`ncouldn't find manifest for '$app@$version'"
} }
ensure (usermanifestsdir) | out-null
try { try {
Invoke-AutoUpdate $app $manifest_path $manifest $version $(@{ }) Invoke-AutoUpdate $app "$(Convert-Path (usermanifestsdir))\$app.json" $manifest $version $(@{ })
return $manifest_path return Convert-Path (usermanifest $app)
} catch { } catch {
Write-Host -ForegroundColor DarkRed "Could not install $app@$version" write-host -f darkred "Could not install $app@$version"
} }
return $null return $null
@@ -209,6 +156,7 @@ function generate_user_manifest($app, $bucket, $version) {
function url($manifest, $arch) { arch_specific 'url' $manifest $arch } function url($manifest, $arch) { arch_specific 'url' $manifest $arch }
function installer($manifest, $arch) { arch_specific 'installer' $manifest $arch } function installer($manifest, $arch) { arch_specific 'installer' $manifest $arch }
function uninstaller($manifest, $arch) { arch_specific 'uninstaller' $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 hash($manifest, $arch) { arch_specific 'hash' $manifest $arch }
function extract_dir($manifest, $arch) { arch_specific 'extract_dir' $manifest $arch } function extract_dir($manifest, $arch) { arch_specific 'extract_dir' $manifest $arch}
function extract_to($manifest, $arch) { arch_specific 'extract_to' $manifest $arch } function extract_to($manifest, $arch) { arch_specific 'extract_to' $manifest $arch}

View File

@@ -1,17 +1,22 @@
$modulesdir = "$scoopdir\modules"
function install_psmodule($manifest, $dir, $global) { function install_psmodule($manifest, $dir, $global) {
$psmodule = $manifest.psmodule $psmodule = $manifest.psmodule
if (!$psmodule) { return } if (!$psmodule) { return }
$targetdir = ensure (modulesdir $global) if ($global) {
abort 'Installing PowerShell modules globally is not implemented!'
}
ensure_in_psmodulepath $targetdir $global $modulesdir = ensure $modulesdir
ensure_in_psmodulepath $modulesdir $global
$module_name = $psmodule.name $module_name = $psmodule.name
if (!$module_name) { if (!$module_name) {
abort "Invalid manifest: The 'name' property is missing from 'psmodule'." abort "Invalid manifest: The 'name' property is missing from 'psmodule'."
} }
$linkfrom = "$targetdir\$module_name" $linkfrom = "$modulesdir\$module_name"
Write-Host "Installing PowerShell module '$module_name'" Write-Host "Installing PowerShell module '$module_name'"
Write-Host "Linking $(friendly_path $linkfrom) => $(friendly_path $dir)" Write-Host "Linking $(friendly_path $linkfrom) => $(friendly_path $dir)"
@@ -31,9 +36,7 @@ function uninstall_psmodule($manifest, $dir, $global) {
$module_name = $psmodule.name $module_name = $psmodule.name
Write-Host "Uninstalling PowerShell module '$module_name'." Write-Host "Uninstalling PowerShell module '$module_name'."
$targetdir = modulesdir $global $linkfrom = "$modulesdir\$module_name"
$linkfrom = "$targetdir\$module_name"
if (Test-Path $linkfrom) { if (Test-Path $linkfrom) {
Write-Host "Removing $(friendly_path $linkfrom)" Write-Host "Removing $(friendly_path $linkfrom)"
$linkfrom = Convert-Path $linkfrom $linkfrom = Convert-Path $linkfrom
@@ -42,13 +45,15 @@ function uninstall_psmodule($manifest, $dir, $global) {
} }
function ensure_in_psmodulepath($dir, $global) { function ensure_in_psmodulepath($dir, $global) {
$path = Get-EnvVar -Name 'PSModulePath' -Global:$global $path = env 'psmodulepath' $global
if (!$global -and $null -eq $path) { if (!$global -and $null -eq $path) {
$path = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules" $path = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules"
} }
$dir = fullpath $dir
if ($path -notmatch [Regex]::Escape($dir)) { if ($path -notmatch [Regex]::Escape($dir)) {
Write-Output "Adding $(friendly_path $dir) to $(if($global){'global'}else{'your'}) PowerShell module path." Write-Output "Adding $(friendly_path $dir) to $(if($global){'global'}else{'your'}) PowerShell module path."
Set-EnvVar -Name 'PSModulePath' -Value "$dir;$path" -Global:$global env 'psmodulepath' $global "$dir;$path" # for future sessions...
$env:psmodulepath = "$dir;$env:psmodulepath" # for this session
} }
} }

View File

@@ -1,176 +0,0 @@
# System-related functions
## Environment Variables
function Publish-EnvVar {
if (-not ('Win32.NativeMethods' -as [Type])) {
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @'
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageTimeout(
IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
uint fuFlags, uint uTimeout, out UIntPtr lpdwResult
);
'@
}
$HWND_BROADCAST = [IntPtr] 0xffff
$WM_SETTINGCHANGE = 0x1a
$result = [UIntPtr]::Zero
[Win32.NativeMethods]::SendMessageTimeout($HWND_BROADCAST,
$WM_SETTINGCHANGE,
[UIntPtr]::Zero,
'Environment',
2,
5000,
[ref] $result
) | Out-Null
}
function Get-EnvVar {
param(
[string]$Name,
[switch]$Global
)
$registerKey = if ($Global) {
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
} else {
Get-Item -Path 'HKCU:'
}
$envRegisterKey = $registerKey.OpenSubKey('Environment')
$registryValueOption = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
$envRegisterKey.GetValue($Name, $null, $registryValueOption)
}
function Set-EnvVar {
param(
[string]$Name,
[string]$Value,
[switch]$Global
)
$registerKey = if ($Global) {
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
} else {
Get-Item -Path 'HKCU:'
}
$envRegisterKey = $registerKey.OpenSubKey('Environment', $true)
if ($null -eq $Value -or $Value -eq '') {
if ($envRegisterKey.GetValue($Name)) {
$envRegisterKey.DeleteValue($Name)
}
} else {
$registryValueKind = if ($Value.Contains('%')) {
[Microsoft.Win32.RegistryValueKind]::ExpandString
} elseif ($envRegisterKey.GetValue($Name)) {
$envRegisterKey.GetValueKind($Name)
} else {
[Microsoft.Win32.RegistryValueKind]::String
}
$envRegisterKey.SetValue($Name, $Value, $registryValueKind)
}
Publish-EnvVar
}
function Split-PathLikeEnvVar {
param(
[string[]]$Pattern,
[string]$Path
)
if ($null -eq $Path -and $Path -eq '') {
return $null, $null
} else {
$splitPattern = $Pattern.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)
$splitPath = $Path.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)
$inPath = @()
foreach ($p in $splitPattern) {
$inPath += $splitPath.Where({ $_ -like $p })
$splitPath = $splitPath.Where({ $_ -notlike $p })
}
return ($inPath -join ';'), ($splitPath -join ';')
}
}
function Add-Path {
param(
[string[]]$Path,
[string]$TargetEnvVar = 'PATH',
[switch]$Global,
[switch]$Force,
[switch]$Quiet
)
# future sessions
$inPath, $strippedPath = Split-PathLikeEnvVar $Path (Get-EnvVar -Name $TargetEnvVar -Global:$Global)
if (!$inPath -or $Force) {
if (!$Quiet) {
$Path | ForEach-Object {
Write-Host "Adding $(friendly_path $_) to $(if ($Global) {'global'} else {'your'}) path."
}
}
Set-EnvVar -Name $TargetEnvVar -Value ((@($Path) + $strippedPath) -join ';') -Global:$Global
}
# current session
$inPath, $strippedPath = Split-PathLikeEnvVar $Path $env:PATH
if (!$inPath -or $Force) {
$env:PATH = (@($Path) + $strippedPath) -join ';'
}
}
function Remove-Path {
param(
[string[]]$Path,
[string]$TargetEnvVar = 'PATH',
[switch]$Global,
[switch]$Quiet,
[switch]$PassThru
)
# future sessions
$inPath, $strippedPath = Split-PathLikeEnvVar $Path (Get-EnvVar -Name $TargetEnvVar -Global:$Global)
if ($inPath) {
if (!$Quiet) {
$Path | ForEach-Object {
Write-Host "Removing $(friendly_path $_) from $(if ($Global) {'global'} else {'your'}) path."
}
}
Set-EnvVar -Name $TargetEnvVar -Value $strippedPath -Global:$Global
}
# current session
$inSessionPath, $strippedPath = Split-PathLikeEnvVar $Path $env:PATH
if ($inSessionPath) {
$env:PATH = $strippedPath
}
if ($PassThru) {
return $inPath
}
}
## Deprecated functions
function env($name, $global, $val) {
if ($PSBoundParameters.ContainsKey('val')) {
Show-DeprecatedWarning $MyInvocation 'Set-EnvVar'
Set-EnvVar -Name $name -Value $val -Global:$global
} else {
Show-DeprecatedWarning $MyInvocation 'Get-EnvVar'
Get-EnvVar -Name $name -Global:$global
}
}
function strip_path($orig_path, $dir) {
Show-DeprecatedWarning $MyInvocation 'Split-PathLikeEnvVar'
Split-PathLikeEnvVar -Pattern @($dir) -Path $orig_path
}
function add_first_in_path($dir, $global) {
Show-DeprecatedWarning $MyInvocation 'Add-Path'
Add-Path -Path $dir -Global:$global -Force
}
function remove_from_path($dir, $global) {
Show-DeprecatedWarning $MyInvocation 'Remove-Path'
Remove-Path -Path $dir -Global:$global
}

View File

@@ -1,68 +1,114 @@
# Usage: scoop alias <subcommand> [options] [<args>] # Usage: scoop alias add|list|rm [<args>]
# Summary: Manage scoop aliases # Summary: Manage scoop aliases
# Help: Available subcommands: add, rm, list. # Help: Add, remove or list Scoop aliases
# #
# Aliases are custom Scoop subcommands that can be created to make common tasks easier. # Aliases are custom Scoop subcommands that can be created to make common tasks
# easier.
# #
# To add an alias: # To add an Alias:
# scoop alias add <name> <command> <description>
# #
# scoop alias add <name> <command> [<description>] # e.g.:
# # scoop alias add rm 'scoop uninstall $args[0]' 'Uninstalls an app'
# e.g., # scoop alias add upgrade 'scoop update *' 'Updates all apps, just like brew or apt'
#
# scoop alias add rm 'scoop uninstall $args[0]' 'Uninstall an app'
# scoop alias add upgrade 'scoop update *' 'Update all apps, just like "brew" or "apt"'
#
# To remove an alias:
#
# scoop alias rm <name>
#
# To list all aliases:
#
# scoop alias list [-v|--verbose]
# #
# Options: # Options:
# -v, --verbose Show alias description and table headers (works only for "list") # -v, --verbose Show alias description and table headers (works only for 'list')
param($SubCommand) param(
[String]$opt,
[String]$name,
[String]$command,
[String]$description,
[Switch]$verbose = $false
)
. "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\install.ps1" # shim related
$SubCommands = @('add', 'rm', 'list') $script:config_alias = 'alias'
if ($SubCommand -notin $SubCommands) {
if (!$SubCommand) { function init_alias_config {
error '<subcommand> missing' $aliases = get_config $script:config_alias
if ($aliases) {
$aliases
} else { } else {
error "'$SubCommand' is not one of available subcommands: $($SubCommands -join ', ')" New-Object -TypeName PSObject
} }
my_usage
exit 1
} }
$opt, $other, $err = getopt $Args 'v' 'verbose' function add_alias($name, $command) {
if ($err) { "scoop alias: $err"; exit 1 } if (!$command) {
abort "Can't create an empty alias."
$name, $command, $description = $other
$verbose = $opt.v -or $opt.verbose
switch ($SubCommand) {
'add' {
if (!$name -or !$command) {
error "<name> and <command> must be specified for subcommand 'add'"
exit 1
} }
add_alias $name $command $description
# get current aliases from config
$aliases = init_alias_config
if ($aliases.$name) {
abort "Alias $name already exists."
} }
'rm' {
$alias_file = "scoop-$name"
# generate script
$shimdir = shimdir $false
$script =
@(
"# Summary: $description",
"$command"
) -join "`r`n"
$script | Out-UTF8File "$shimdir\$alias_file.ps1"
# add alias to config
$aliases | Add-Member -MemberType NoteProperty -Name $name -Value $alias_file
set_config $script:config_alias $aliases | Out-Null
}
function rm_alias($name) {
$aliases = init_alias_config
if (!$name) { if (!$name) {
error "<name> must be specified for subcommand 'rm'" abort 'Which alias should be removed?'
exit 1
} }
rm_alias $name
if ($aliases.$name) {
"Removing alias $name..."
rm_shim $aliases.$name (shimdir $false)
$aliases.PSObject.Properties.Remove($name)
set_config $script:config_alias $aliases | Out-Null
} else {
abort "Alias $name doesn't exist."
} }
'list' { }
list_aliases $verbose
function list_aliases {
$aliases = @()
(init_alias_config).PSObject.Properties.GetEnumerator() | ForEach-Object {
$content = Get-Content (command_path $_.Name)
$command = ($content | Select-Object -Skip 1).Trim()
$summary = (summary $content).Trim()
$aliases += New-Object psobject -Property @{Name = $_.name; Summary = $summary; Command = $command }
} }
if (!$aliases.count) {
info "No alias found."
}
$aliases = $aliases.GetEnumerator() | Sort-Object Name
if ($verbose) {
return $aliases | Select-Object Name, Command, Summary
} else {
return $aliases | Select-Object Name, Command
}
}
switch ($opt) {
'add' { add_alias $name $command }
'rm' { rm_alias $name }
'list' { list_aliases }
default { my_usage; exit 1 }
} }
exit 0 exit 0

View File

@@ -19,15 +19,6 @@
# scoop bucket known # scoop bucket known
param($cmd, $name, $repo) param($cmd, $name, $repo)
if (get_config NO_JUNCTION) {
. "$PSScriptRoot\..\lib\versions.ps1"
}
if (get_config USE_SQLITE_CACHE) {
. "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\database.ps1"
}
$usage_add = 'usage: scoop bucket add <name> [<repo>]' $usage_add = 'usage: scoop bucket add <name> [<repo>]'
$usage_rm = 'usage: scoop bucket rm <name>' $usage_rm = 'usage: scoop bucket rm <name>'

View File

@@ -16,7 +16,7 @@ param($cmd)
function cacheinfo($file) { function cacheinfo($file) {
$app, $version, $url = $file.Name -split '#' $app, $version, $url = $file.Name -split '#'
New-Object PSObject -Property @{ Name = $app; Version = $version; Length = $file.Length } New-Object PSObject -Property @{ Name = $app; Version = $version; Length = $file.Length; URL = $url }
} }
function cacheshow($app) { function cacheshow($app) {
@@ -28,7 +28,7 @@ function cacheshow($app) {
$files = @(Get-ChildItem $cachedir | Where-Object -Property Name -Value "^$app#" -Match) $files = @(Get-ChildItem $cachedir | Where-Object -Property Name -Value "^$app#" -Match)
$totalLength = ($files | Measure-Object -Property Length -Sum).Sum $totalLength = ($files | Measure-Object -Property Length -Sum).Sum
$files | ForEach-Object { cacheinfo $_ } | Select-Object Name, Version, Length $files | ForEach-Object { cacheinfo $_ } | Select-Object Name, Version, Length, URL
Write-Host "Total: $($files.Length) $(pluralize $files.Length 'file' 'files'), $(filesize $totalLength)" -ForegroundColor Yellow Write-Host "Total: $($files.Length) $(pluralize $files.Length 'file' 'files'), $(filesize $totalLength)" -ForegroundColor Yellow
} }
@@ -48,7 +48,7 @@ function cacheremove($app) {
$files | ForEach-Object { $files | ForEach-Object {
$curr = cacheinfo $_ $curr = cacheinfo $_
Write-Host "Removing $($_.Name)..." Write-Host "Removing $($curr.URL)..."
Remove-Item $_.FullName Remove-Item $_.FullName
if(Test-Path "$cachedir\$($curr.Name).txt") { if(Test-Path "$cachedir\$($curr.Name).txt") {
Remove-Item "$cachedir\$($curr.Name).txt" Remove-Item "$cachedir\$($curr.Name).txt"

View File

@@ -7,9 +7,7 @@
param($app) param($app)
. "$PSScriptRoot\..\lib\json.ps1" # 'ConvertToPrettyJson' . "$PSScriptRoot\..\lib\json.ps1" # 'ConvertToPrettyJson'
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' . "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest'
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-UserAgent'
if (!$app) { error '<app> missing'; my_usage; exit 1 } if (!$app) { error '<app> missing'; my_usage; exit 1 }
@@ -23,7 +21,7 @@ if ($manifest) {
$manifest | ConvertToPrettyJson $manifest | ConvertToPrettyJson
} }
} else { } else {
abort "Couldn't find manifest for '$app'$(if($bucket) { " from '$bucket' bucket" } elseif($url) { " at '$url'" })." abort "Couldn't find manifest for '$app'$(if($url) { " at the URL $url" })."
} }
exit $exitCode exit $exitCode

View File

@@ -10,7 +10,7 @@ $defenderIssues = 0
$adminPrivileges = ([System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) $adminPrivileges = ([System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
if ($adminPrivileges -and $env:USERNAME -ne 'WDAGUtilityAccount') { if ($adminPrivileges) {
$defenderIssues += !(check_windows_defender $false) $defenderIssues += !(check_windows_defender $false)
$defenderIssues += !(check_windows_defender $true) $defenderIssues += !(check_windows_defender $true)
} }
@@ -19,18 +19,18 @@ $issues += !(check_main_bucket)
$issues += !(check_long_paths) $issues += !(check_long_paths)
$issues += !(Get-WindowsDeveloperModeStatus) $issues += !(Get-WindowsDeveloperModeStatus)
if (!(Test-HelperInstalled -Helper 7zip) -and !(get_config USE_EXTERNAL_7ZIP)) { if (!(Test-HelperInstalled -Helper 7zip)) {
warn "'7-Zip' is not installed! It's required for unpacking most programs. Please Run 'scoop install 7zip'." error "'7-Zip' is not installed! It's required for unpacking most programs. Please Run 'scoop install 7zip' or 'scoop install 7zip-zstd'."
$issues++ $issues++
} }
if (!(Test-HelperInstalled -Helper Innounp)) { if (!(Test-HelperInstalled -Helper Innounp)) {
warn "'Inno Setup Unpacker' is not installed! It's required for unpacking InnoSetup files. Please run 'scoop install innounp'." error "'Inno Setup Unpacker' is not installed! It's required for unpacking InnoSetup files. Please run 'scoop install innounp'."
$issues++ $issues++
} }
if (!(Test-HelperInstalled -Helper Dark)) { if (!(Test-HelperInstalled -Helper Dark)) {
warn "'dark' is not installed! It's required for unpacking installers created with the WiX Toolset. Please run 'scoop install dark' or 'scoop install wixtoolset'." error "'dark' is not installed! It's required for unpacking installers created with the WiX Toolset. Please run 'scoop install dark' or 'scoop install wixtoolset'."
$issues++ $issues++
} }

View File

@@ -27,9 +27,6 @@
# use_lessmsi: $true|$false # use_lessmsi: $true|$false
# Prefer lessmsi utility over native msiexec. # Prefer lessmsi utility over native msiexec.
# #
# use_sqlite_cache: $true|$false
# Use SQLite database for caching. This is useful for speeding up 'scoop search' and 'scoop shim' commands.
#
# no_junction: $true|$false # no_junction: $true|$false
# The 'current' version alias will not be used. Shims and shortcuts will point to specific version instead. # The 'current' version alias will not be used. Shims and shortcuts will point to specific version instead.
# #
@@ -118,11 +115,6 @@
# Nightly version is formatted as 'nightly-yyyyMMdd' and will be updated after one day if this is set to $true. # Nightly version is formatted as 'nightly-yyyyMMdd' and will be updated after one day if this is set to $true.
# Otherwise, nightly version will not be updated unless `--force` is used. # Otherwise, nightly version will not be updated unless `--force` is used.
# #
# use_isolated_path: $true|$false|[string]
# When set to $true, Scoop will use `SCOOP_PATH` environment variable to store apps' `PATH`s.
# When set to arbitrary non-empty string, Scoop will use that string as the environment variable name instead.
# This is useful when you want to isolate Scoop from the system `PATH`.
#
# ARIA2 configuration # ARIA2 configuration
# ------------------- # -------------------
# #
@@ -159,12 +151,30 @@ if (!$name) {
} elseif ($name -like '--help') { } elseif ($name -like '--help') {
my_usage my_usage
} elseif ($name -like 'rm') { } elseif ($name -like 'rm') {
# NOTE Scoop config file migration. Remove this after 2023/6/30
if ($value -notin 'SCOOP_REPO', 'SCOOP_BRANCH' -and $value -in $newConfigNames.Keys) {
warn ('Config option "{0}" is deprecated, please use "{1}" instead next time.' -f $value, $newConfigNames.$value)
$value = $newConfigNames.$value
}
# END NOTE
set_config $value $null | Out-Null set_config $value $null | Out-Null
Write-Host "'$value' has been removed" Write-Host "'$value' has been removed"
} elseif ($null -ne $value) { } elseif ($null -ne $value) {
# NOTE Scoop config file migration. Remove this after 2023/6/30
if ($name -notin 'SCOOP_REPO', 'SCOOP_BRANCH' -and $name -in $newConfigNames.Keys) {
warn ('Config option "{0}" is deprecated, please use "{1}" instead next time.' -f $name, $newConfigNames.$name)
$name = $newConfigNames.$name
}
# END NOTE
set_config $name $value | Out-Null set_config $name $value | Out-Null
Write-Host "'$name' has been set to '$value'" Write-Host "'$name' has been set to '$value'"
} else { } else {
# NOTE Scoop config file migration. Remove this after 2023/6/30
if ($name -notin 'SCOOP_REPO', 'SCOOP_BRANCH' -and $name -in $newConfigNames.Keys) {
warn ('Config option "{0}" is deprecated, please use "{1}" instead next time.' -f $name, $newConfigNames.$name)
$name = $newConfigNames.$name
}
# END NOTE
$value = get_config $name $value = get_config $name
if($null -eq $value) { if($null -eq $value) {
Write-Host "'$name' is not set" Write-Host "'$name' is not set"

View File

@@ -59,7 +59,7 @@ function choose_item($list, $query) {
} }
if (!$url) { if (!$url) {
& "$PSScriptRoot\scoop-help.ps1" create scoop help create
} else { } else {
create_manifest $url create_manifest $url
} }

View File

@@ -3,9 +3,7 @@
. "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\getopt.ps1"
. "$PSScriptRoot\..\lib\depends.ps1" # 'Get-Dependency' . "$PSScriptRoot\..\lib\depends.ps1" # 'Get-Dependency'
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' (indirectly) . "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' (indirectly)
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-UserAgent'
$opt, $apps, $err = getopt $args 'a:' 'arch=' $opt, $apps, $err = getopt $args 'a:' 'arch='
$app = $apps[0] $app = $apps[0]
@@ -22,14 +20,7 @@ try {
$deps = @() $deps = @()
Get-Dependency $app $architecture | ForEach-Object { Get-Dependency $app $architecture | ForEach-Object {
$dep = [ordered]@{} $dep = [ordered]@{}
$dep.Source, $dep.Name = $_ -split '/'
$app, $null, $bucket, $url = Get-Manifest $_
if (!$url) {
$bucket, $app = $_ -split '/'
}
$dep.Source = if ($url) { $url } else { $bucket }
$dep.Name = $app
$deps += [PSCustomObject]$dep $deps += [PSCustomObject]$dep
} }
$deps $deps

View File

@@ -15,24 +15,20 @@
# #
# Options: # Options:
# -f, --force Force download (overwrite cache) # -f, --force Force download (overwrite cache)
# -s, --skip-hash-check Skip hash verification (use with caution!) # -h, --no-hash-check Skip hash verification (use with caution!)
# -u, --no-update-scoop Don't update Scoop before downloading if it's outdated # -u, --no-update-scoop Don't update Scoop before downloading if it's outdated
# -a, --arch <32bit|64bit|arm64> Use the specified architecture, if the app supports it # -a, --arch <32bit|64bit|arm64> Use the specified architecture, if the app supports it
. "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\getopt.ps1"
. "$PSScriptRoot\..\lib\json.ps1" # 'autoupdate.ps1' (indirectly) . "$PSScriptRoot\..\lib\json.ps1" # 'autoupdate.ps1' (indirectly)
. "$PSScriptRoot\..\lib\autoupdate.ps1" # 'generate_user_manifest' (indirectly) . "$PSScriptRoot\..\lib\autoupdate.ps1" # 'generate_user_manifest' (indirectly)
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
. "$PSScriptRoot\..\lib\manifest.ps1" # 'generate_user_manifest' 'Get-Manifest' . "$PSScriptRoot\..\lib\manifest.ps1" # 'generate_user_manifest' 'Get-Manifest'
. "$PSScriptRoot\..\lib\download.ps1" . "$PSScriptRoot\..\lib\install.ps1"
if (get_config USE_SQLITE_CACHE) {
. "$PSScriptRoot\..\lib\database.ps1"
}
$opt, $apps, $err = getopt $args 'fsua:' 'force', 'skip-hash-check', 'no-update-scoop', 'arch=' $opt, $apps, $err = getopt $args 'fhua:' 'force', 'no-hash-check', 'no-update-scoop', 'arch='
if ($err) { error "scoop download: $err"; exit 1 } if ($err) { error "scoop download: $err"; exit 1 }
$check_hash = !($opt.s -or $opt.'skip-hash-check') $check_hash = !($opt.h -or $opt.'no-hash-check')
$use_cache = !($opt.f -or $opt.force) $use_cache = !($opt.f -or $opt.force)
$architecture = Get-DefaultArchitecture $architecture = Get-DefaultArchitecture
try { try {
@@ -47,7 +43,7 @@ if (is_scoop_outdated) {
if ($opt.u -or $opt.'no-update-scoop') { if ($opt.u -or $opt.'no-update-scoop') {
warn "Scoop is out of date." warn "Scoop is out of date."
} else { } else {
& "$PSScriptRoot\scoop-update.ps1" scoop update
} }
} }
@@ -74,7 +70,7 @@ foreach ($curr_app in $apps) {
} }
if(!$manifest) { if(!$manifest) {
error "Couldn't find manifest for '$app'$(if($bucket) { " from '$bucket' bucket" } elseif($url) { " at '$url'" })." error "Couldn't find manifest for '$app'$(if($url) { " at the URL $url" })."
continue continue
} }
$version = $manifest.version $version = $manifest.version

View File

@@ -29,13 +29,14 @@ if ($global -and !(is_admin)) {
exit 1 exit 1
} }
foreach ($app in $apps) { $apps | ForEach-Object {
$app = $_
if ($app -eq 'scoop') { if ($app -eq 'scoop') {
$hold_update_until = [System.DateTime]::Now.AddDays(1) $hold_update_until = [System.DateTime]::Now.AddDays(1)
set_config HOLD_UPDATE_UNTIL $hold_update_until.ToString('o') | Out-Null set_config HOLD_UPDATE_UNTIL $hold_update_until.ToString('o') | Out-Null
success "$app is now held and might not be updated until $($hold_update_until.ToLocalTime())." success "$app is now held and might not be updated until $($hold_update_until.ToLocalTime())."
continue return
} }
if (!(installed $app $global)) { if (!(installed $app $global)) {
if ($global) { if ($global) {
@@ -43,26 +44,18 @@ foreach ($app in $apps) {
} else { } else {
error "'$app' is not installed." error "'$app' is not installed."
} }
continue return
} }
if (get_config NO_JUNCTION) { if (get_config NO_JUNCTION){
$version = Select-CurrentVersion -App $app -Global:$global $version = Select-CurrentVersion -App $app -Global:$global
} else { } else {
$version = 'current' $version = 'current'
} }
$dir = versiondir $app $version $global $dir = versiondir $app $version $global
$json = install_info $app $version $global $json = install_info $app $version $global
if (!$json) {
error "Failed to hold '$app'."
continue
}
$install = @{} $install = @{}
$json | Get-Member -MemberType Properties | ForEach-Object { $install.Add($_.Name, $json.($_.Name)) } $json | Get-Member -MemberType Properties | ForEach-Object { $install.Add($_.Name, $json.($_.Name)) }
if ($install.hold) {
info "'$app' is already held."
continue
}
$install.hold = $true $install.hold = $true
save_install_info $install $dir save_install_info $install $dir
success "$app is now held and can not be updated anymore." success "$app is now held and can not be updated anymore."

View File

@@ -2,9 +2,7 @@
# Summary: Opens the app homepage # Summary: Opens the app homepage
param($app) param($app)
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' . "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest'
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-UserAgent'
if ($app) { if ($app) {
$null, $manifest, $bucket, $null = Get-Manifest $app $null, $manifest, $bucket, $null = Get-Manifest $app

View File

@@ -34,19 +34,20 @@ foreach ($item in $import.buckets) {
} }
foreach ($item in $import.apps) { foreach ($item in $import.apps) {
$instArgs = @()
$holdArgs = @()
$info = $item.Info -Split ', ' $info = $item.Info -Split ', '
if ('Global install' -in $info) { $global = if ('Global install' -in $info) {
$instArgs += '--global' ' --global'
$holdArgs += '--global' } else {
''
} }
if ('64bit' -in $info -and '64bit' -ne $def_arch) { $arch = if ('64bit' -in $info -and '64bit' -ne $def_arch) {
$instArgs += '--arch', '64bit' ' --arch 64bit'
} elseif ('32bit' -in $info -and '32bit' -ne $def_arch) { } elseif ('32bit' -in $info -and '32bit' -ne $def_arch) {
$instArgs += '--arch', '32bit' ' --arch 32bit'
} elseif ('arm64' -in $info -and 'arm64' -ne $def_arch) { } elseif ('arm64' -in $info -and 'arm64' -ne $def_arch) {
$instArgs += '--arch', 'arm64' ' --arch arm64'
} else {
''
} }
$app = if ($item.Source -in $bucket_names) { $app = if ($item.Source -in $bucket_names) {
@@ -57,9 +58,9 @@ foreach ($item in $import.apps) {
$item.Source $item.Source
} }
& "$PSScriptRoot\scoop-install.ps1" $app @instArgs & "$PSScriptRoot\scoop-install.ps1" $app$global$arch
if ('Held package' -in $info) { if ('Held package' -in $info) {
& "$PSScriptRoot\scoop-hold.ps1" $item.Name @holdArgs & "$PSScriptRoot\scoop-hold.ps1" $($item.Name)$global
} }
} }

View File

@@ -1,15 +1,13 @@
# Usage: scoop info <app> [options] # Usage: scoop info <app> [--verbose]
# Summary: Display information about an app # Summary: Display information about an app
# Help: Options: # Options:
# -v, --verbose Show full paths and URLs # -v, --verbose Show full paths and URLs
. "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\getopt.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' . "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest'
. "$PSScriptRoot\..\lib\versions.ps1" # 'Get-InstalledVersion', 'Select-CurrentVersion' . "$PSScriptRoot\..\lib\versions.ps1" # 'Get-InstalledVersion'
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-RemoteFileSize'
$opt, $app, $err = getopt $args 'v' 'verbose' $opt, $app, $err = getopt $args 'v' 'verbose'
$original_app = $app
if ($err) { error "scoop info: $err"; exit 1 } if ($err) { error "scoop info: $err"; exit 1 }
$verbose = $opt.v -or $opt.verbose $verbose = $opt.v -or $opt.verbose
@@ -24,7 +22,7 @@ if (!$manifest) {
$global = installed $app $true $global = installed $app $true
$status = app_status $app $global $status = app_status $app $global
$install = install_info $app $status.version $global $install = install_info $app $status.version $global
$status.installed = ($bucket -and $install.bucket -eq $bucket) -or (installed $app) $status.installed = $bucket -and $install.bucket -eq $bucket
$version_output = $manifest.version $version_output = $manifest.version
$manifest_file = if ($bucket) { $manifest_file = if ($bucket) {
manifest_path $app $bucket manifest_path $app $bucket
@@ -32,30 +30,12 @@ $manifest_file = if ($bucket) {
$url $url
} }
# Standalone and Source detection
if ((Test-Path $original_app) -or ($original_app -match '^(ht|f)tps?://|\\\\')) {
$standalone = $true
if (Test-Path $original_app) {
$original_app = (Get-AbsolutePath "$original_app")
}
if ($install.url) {
if (Test-Path $install.url) {
$install_url = (Get-AbsolutePath $install.url)
} else {
$install_url = $install.url
}
}
if ($original_app -eq $install_url) {
$same_source = $true
}
}
if ($verbose) { if ($verbose) {
$dir = currentdir $app $global $dir = currentdir $app $global
$original_dir = versiondir $app $manifest.version $global $original_dir = versiondir $app $manifest.version $global
$persist_dir = persistdir $app $global $persist_dir = persistdir $app $global
} else { } else {
$dir, $original_dir, $persist_dir = '<root>', '<root>', '<root>' $dir, $original_dir, $persist_dir = "<root>", "<root>", "<root>"
} }
if ($status.installed) { if ($status.installed) {
@@ -63,39 +43,21 @@ if ($status.installed) {
if ($install.url) { if ($install.url) {
$manifest_file = $install.url $manifest_file = $install.url
} }
if ($status.deprecated) { if ($status.version -eq $manifest.version) {
$manifest_file = $status.deprecated
} elseif ($standalone -and !$same_source) {
$version_output = $manifest.version
} elseif ($status.version -eq $manifest.version) {
$version_output = $status.version $version_output = $status.version
} else { } else {
$version_output = "$($status.version) (Update to $($manifest.version) available)" $version_output = "$($status.version) (Update to $($manifest.version) available)"
} }
} }
$item = [ordered]@{ Name = $app } $item = [ordered]@{ Name = $app }
if ($status.deprecated) {
$item.Name += ' (DEPRECATED)'
}
if ($manifest.description) { if ($manifest.description) {
$item.Description = $manifest.description $item.Description = $manifest.description
} }
$item.Version = $version_output $item.Version = $version_output
if ($bucket) {
$item.Source = if ($standalone) { $item.Bucket = $bucket
$original_app
} else {
if ($install.bucket) {
$install.bucket
} elseif ($install.url) {
$install.url
} else {
$bucket
}
} }
if ($manifest.homepage) { if ($manifest.homepage) {
$item.Website = $manifest.homepage.TrimEnd('/') $item.Website = $manifest.homepage.TrimEnd('/')
} }
@@ -107,7 +69,7 @@ if ($manifest.license) {
$manifest.license $manifest.license
} elseif ($manifest.license -match '[|,]') { } elseif ($manifest.license -match '[|,]') {
if ($verbose) { if ($verbose) {
"$($manifest.license) ($(($manifest.license -Split '\||,' | ForEach-Object { "https://spdx.org/licenses/$_.html" }) -join ', '))" "$($manifest.license) ($(($manifest.license -Split "\||," | ForEach-Object { "https://spdx.org/licenses/$_.html" }) -join ', '))"
} else { } else {
$manifest.license $manifest.license
} }
@@ -122,7 +84,7 @@ if ($manifest.depends) {
if (Test-Path $manifest_file) { if (Test-Path $manifest_file) {
if (Get-Command git -ErrorAction Ignore) { if (Get-Command git -ErrorAction Ignore) {
$gitinfo = (Invoke-Git -Path (Split-Path $manifest_file) -ArgumentList @('log', '-1', '-s', '--format=%aD#%an', $manifest_file) 2> $null) -Split '#' $gitinfo = (Invoke-Git -Path (Split-Path $manifest_file) -ArgumentList @('log', '-1', '-s', "--format='%aD#%an'", $manifest_file) 2> $null) -Split '#'
} }
if ($gitinfo) { if ($gitinfo) {
$item.'Updated at' = $gitinfo[0] | Get-Date $item.'Updated at' = $gitinfo[0] | Get-Date
@@ -138,13 +100,11 @@ if ($verbose) { $item.Manifest = $manifest_file }
if ($status.installed) { if ($status.installed) {
# Show installed versions # Show installed versions
if (!$standalone -or $same_source) {
$installed_output = @() $installed_output = @()
Get-InstalledVersion -AppName $app -Global:$global | ForEach-Object { Get-InstalledVersion -AppName $app -Global:$global | ForEach-Object {
$installed_output += if ($verbose) { versiondir $app $_ $global } else { "$_$(if ($global) { ' *global*' })" } $installed_output += if ($verbose) { versiondir $app $_ $global } else { "$_$(if ($global) { " *global*" })" }
} }
$item.Installed = $installed_output -join "`n" $item.Installed = $installed_output -join "`n"
}
if ($verbose) { if ($verbose) {
# Show size of installation # Show size of installation
@@ -152,7 +112,7 @@ if ($status.installed) {
# Collect file list from each location # Collect file list from each location
$appFiles = Get-ChildItem $appsdir -Filter $app $appFiles = Get-ChildItem $appsdir -Filter $app
$currentFiles = Get-ChildItem $appFiles.FullName -Filter (Select-CurrentVersion $app $global) $currentFiles = Get-ChildItem $appFiles -Filter (Select-CurrentVersion $app $global)
$persistFiles = Get-ChildItem $persist_dir -ErrorAction Ignore # Will fail if app does not persist data $persistFiles = Get-ChildItem $persist_dir -ErrorAction Ignore # Will fail if app does not persist data
$cacheFiles = Get-ChildItem $cachedir -Filter "$app#*" $cacheFiles = Get-ChildItem $cachedir -Filter "$app#*"
@@ -160,7 +120,7 @@ if ($status.installed) {
$fileTotals = @() $fileTotals = @()
foreach ($fileType in ($appFiles, $currentFiles, $persistFiles, $cacheFiles)) { foreach ($fileType in ($appFiles, $currentFiles, $persistFiles, $cacheFiles)) {
if ($null -ne $fileType) { if ($null -ne $fileType) {
$fileSum = (Get-ChildItem $fileType.FullName -Recurse -File | Measure-Object -Property Length -Sum).Sum $fileSum = (Get-ChildItem $fileType -Recurse | Measure-Object -Property Length -Sum).Sum
$fileTotals += coalesce $fileSum 0 $fileTotals += coalesce $fileSum 0
} else { } else {
$fileTotals += 0 $fileTotals += 0
@@ -200,13 +160,13 @@ if ($status.installed) {
$totalPackage = 0 $totalPackage = 0
foreach ($url in @(url $manifest (Get-DefaultArchitecture))) { foreach ($url in @(url $manifest (Get-DefaultArchitecture))) {
try { try {
if (Test-Path (cache_path $app $manifest.version $url)) { if (Test-Path (fullpath (cache_path $app $manifest.version $url))) {
$cached = ' (latest version is cached)' $cached = " (latest version is cached)"
} else { } else {
$cached = $null $cached = $null
} }
$urlLength = Get-RemoteFileSize $url [int]$urlLength = (Invoke-WebRequest $url -Method Head).Headers.'Content-Length'[0]
$totalPackage += $urlLength $totalPackage += $urlLength
} catch [System.Management.Automation.RuntimeException] { } catch [System.Management.Automation.RuntimeException] {
$totalPackage = 0 $totalPackage = 0
@@ -236,7 +196,7 @@ if ($binaries) {
$binary_output += $_ $binary_output += $_
} }
} }
$item.Binaries = $binary_output -join ' | ' $item.Binaries = $binary_output -join " | "
} }
$shortcuts = @(arch_specific 'shortcuts' $manifest $install.architecture) $shortcuts = @(arch_specific 'shortcuts' $manifest $install.architecture)
if ($shortcuts) { if ($shortcuts) {
@@ -244,13 +204,13 @@ if ($shortcuts) {
$shortcuts | ForEach-Object { $shortcuts | ForEach-Object {
$shortcut_output += $_[1] $shortcut_output += $_[1]
} }
$item.Shortcuts = $shortcut_output -join ' | ' $item.Shortcuts = $shortcut_output -join " | "
} }
$env_set = arch_specific 'env_set' $manifest $install.architecture $env_set = arch_specific 'env_set' $manifest $install.architecture
if ($env_set) { if ($env_set) {
$env_vars = @() $env_vars = @()
$env_set | Get-Member -member noteproperty | ForEach-Object { $env_set | Get-Member -member noteproperty | ForEach-Object {
$env_vars += "$($_.name) = $(substitute $env_set.$($_.name) @{ '$dir' = $dir })" $env_vars += "$($_.name) = $(format $env_set.$($_.name) @{ "dir" = $dir })"
} }
$item.Environment = $env_vars -join "`n" $item.Environment = $env_vars -join "`n"
} }

View File

@@ -10,44 +10,33 @@
# To install an app from a manifest at a URL: # To install an app from a manifest at a URL:
# scoop install https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/runat.json # scoop install https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/runat.json
# #
# To install a different version of the app from a URL:
# scoop install https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/neovim.json@0.9.0
#
# To install an app from a manifest on your computer # To install an app from a manifest on your computer
# scoop install \path\to\app.json # scoop install \path\to\app.json
# #
# To install an app from a manifest on your computer
# scoop install \path\to\app.json@version
#
# Options: # Options:
# -g, --global Install the app globally # -g, --global Install the app globally
# -i, --independent Don't install dependencies automatically # -i, --independent Don't install dependencies automatically
# -k, --no-cache Don't use the download cache # -k, --no-cache Don't use the download cache
# -s, --skip-hash-check Skip hash validation (use with caution!)
# -u, --no-update-scoop Don't update Scoop before installing if it's outdated # -u, --no-update-scoop Don't update Scoop before installing if it's outdated
# -s, --skip Skip hash validation (use with caution!)
# -a, --arch <32bit|64bit|arm64> Use the specified architecture, if the app supports it # -a, --arch <32bit|64bit|arm64> Use the specified architecture, if the app supports it
. "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\getopt.ps1"
. "$PSScriptRoot\..\lib\json.ps1" # 'autoupdate.ps1' 'manifest.ps1' (indirectly) . "$PSScriptRoot\..\lib\json.ps1" # 'autoupdate.ps1' 'manifest.ps1' (indirectly)
. "$PSScriptRoot\..\lib\autoupdate.ps1" # 'generate_user_manifest' (indirectly) . "$PSScriptRoot\..\lib\autoupdate.ps1" # 'generate_user_manifest' (indirectly)
. "$PSScriptRoot\..\lib\manifest.ps1" # 'generate_user_manifest' 'Get-Manifest' 'Select-CurrentVersion' (indirectly) . "$PSScriptRoot\..\lib\manifest.ps1" # 'generate_user_manifest' 'Get-Manifest' 'Select-CurrentVersion' (indirectly)
. "$PSScriptRoot\..\lib\system.ps1"
. "$PSScriptRoot\..\lib\install.ps1" . "$PSScriptRoot\..\lib\install.ps1"
. "$PSScriptRoot\..\lib\download.ps1"
. "$PSScriptRoot\..\lib\decompress.ps1" . "$PSScriptRoot\..\lib\decompress.ps1"
. "$PSScriptRoot\..\lib\shortcuts.ps1" . "$PSScriptRoot\..\lib\shortcuts.ps1"
. "$PSScriptRoot\..\lib\psmodules.ps1" . "$PSScriptRoot\..\lib\psmodules.ps1"
. "$PSScriptRoot\..\lib\versions.ps1" . "$PSScriptRoot\..\lib\versions.ps1"
. "$PSScriptRoot\..\lib\depends.ps1" . "$PSScriptRoot\..\lib\depends.ps1"
if (get_config USE_SQLITE_CACHE) {
. "$PSScriptRoot\..\lib\database.ps1"
}
$opt, $apps, $err = getopt $args 'giksua:' 'global', 'independent', 'no-cache', 'skip-hash-check', 'no-update-scoop', 'arch=' $opt, $apps, $err = getopt $args 'gikusa:' 'global', 'independent', 'no-cache', 'no-update-scoop', 'skip', 'arch='
if ($err) { "scoop install: $err"; exit 1 } if ($err) { "scoop install: $err"; exit 1 }
$global = $opt.g -or $opt.global $global = $opt.g -or $opt.global
$check_hash = !($opt.s -or $opt.'skip-hash-check') $check_hash = !($opt.s -or $opt.skip)
$independent = $opt.i -or $opt.independent $independent = $opt.i -or $opt.independent
$use_cache = !($opt.k -or $opt.'no-cache') $use_cache = !($opt.k -or $opt.'no-cache')
$architecture = Get-DefaultArchitecture $architecture = Get-DefaultArchitecture
@@ -67,7 +56,7 @@ if (is_scoop_outdated) {
if ($opt.u -or $opt.'no-update-scoop') { if ($opt.u -or $opt.'no-update-scoop') {
warn "Scoop is out of date." warn "Scoop is out of date."
} else { } else {
& "$PSScriptRoot\scoop-update.ps1" scoop update
} }
} }
@@ -92,7 +81,7 @@ $specific_versions = $apps | Where-Object {
} }
# compare object does not like nulls # compare object does not like nulls
if ($specific_versions.Count -gt 0) { if ($specific_versions.length -gt 0) {
$difference = Compare-Object -ReferenceObject $apps -DifferenceObject $specific_versions -PassThru $difference = Compare-Object -ReferenceObject $apps -DifferenceObject $specific_versions -PassThru
} else { } else {
$difference = $apps $difference = $apps
@@ -101,13 +90,13 @@ if ($specific_versions.Count -gt 0) {
$specific_versions_paths = $specific_versions | ForEach-Object { $specific_versions_paths = $specific_versions | ForEach-Object {
$app, $bucket, $version = parse_app $_ $app, $bucket, $version = parse_app $_
if (installed_manifest $app $version) { if (installed_manifest $app $version) {
warn "'$app' ($version) is already installed.`nUse 'scoop update $app$(if ($global) { ' --global' })' to install a new version." warn "'$app' ($version) is already installed.`nUse 'scoop update $app$(if ($global) { " --global" })' to install a new version."
continue continue
} }
generate_user_manifest $app $bucket $version generate_user_manifest $app $bucket $version
} }
$apps = @((@($specific_versions_paths) + $difference) | Where-Object { $_ } | Select-Object -Unique) $apps = @(($specific_versions_paths + $difference) | Where-Object { $_ } | Sort-Object -Unique)
# remember which were explictly requested so that we can # remember which were explictly requested so that we can
# differentiate after dependencies are added # differentiate after dependencies are added

View File

@@ -5,9 +5,8 @@ param($query)
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion' . "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
. "$PSScriptRoot\..\lib\manifest.ps1" # 'parse_json' 'Select-CurrentVersion' (indirectly) . "$PSScriptRoot\..\lib\manifest.ps1" # 'parse_json' 'Select-CurrentVersion' (indirectly)
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-UserAgent'
$defaultArchitecture = Get-DefaultArchitecture $def_arch = Get-DefaultArchitecture
if (-not (Get-FormatData ScoopApps)) { if (-not (Get-FormatData ScoopApps)) {
Update-FormatData "$PSScriptRoot\..\supporting\formats\ScoopTypes.Format.ps1xml" Update-FormatData "$PSScriptRoot\..\supporting\formats\ScoopTypes.Format.ps1xml"
} }
@@ -48,11 +47,10 @@ $apps | Where-Object { !$query -or ($_.name -match $query) } | ForEach-Object {
$item.Updated = $updated $item.Updated = $updated
$info = @() $info = @()
if ((app_status $app $global).deprecated) { $info += 'Deprecated package'}
if ($global) { $info += 'Global install' } if ($global) { $info += 'Global install' }
if (failed $app $global) { $info += 'Install failed' } if (failed $app $global) { $info += 'Install failed' }
if ($install_info.hold) { $info += 'Held package' } if ($install_info.hold) { $info += 'Held package' }
if ($install_info.architecture -and $defaultArchitecture -ne $install_info.architecture) { if ($install_info.architecture -and $def_arch -ne $install_info.architecture) {
$info += $install_info.architecture $info += $install_info.architecture
} }
$item.Info = $info -join ', ' $item.Info = $info -join ', '

View File

@@ -8,7 +8,6 @@
. "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\getopt.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Select-CurrentVersion' (indirectly) . "$PSScriptRoot\..\lib\manifest.ps1" # 'Select-CurrentVersion' (indirectly)
. "$PSScriptRoot\..\lib\system.ps1" # 'env_add_path' (indirectly)
. "$PSScriptRoot\..\lib\install.ps1" . "$PSScriptRoot\..\lib\install.ps1"
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion' . "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
. "$PSScriptRoot\..\lib\shortcuts.ps1" . "$PSScriptRoot\..\lib\shortcuts.ps1"
@@ -70,7 +69,7 @@ $apps | ForEach-Object {
#region Workaround for #2952 #region Workaround for #2952
if (test_running_process $app $global) { if (test_running_process $app $global) {
return continue
} }
#endregion Workaround for #2952 #endregion Workaround for #2952
@@ -80,11 +79,8 @@ $apps | ForEach-Object {
$dir = link_current $dir $dir = link_current $dir
create_shims $manifest $dir $global $architecture create_shims $manifest $dir $global $architecture
create_startmenu_shortcuts $manifest $dir $global $architecture create_startmenu_shortcuts $manifest $dir $global $architecture
# unset all potential old env before re-adding
env_rm_path $manifest $dir $global $architecture
env_rm $manifest $global $architecture
env_add_path $manifest $dir $global $architecture env_add_path $manifest $dir $global $architecture
env_set $manifest $global $architecture env_set $manifest $dir $global $architecture
# unlink all potential old link before re-persisting # unlink all potential old link before re-persisting
unlink_persist_data $manifest $original_dir unlink_persist_data $manifest $original_dir
persist_data $manifest $original_dir $persist_dir persist_data $manifest $original_dir $persist_dir

View File

@@ -3,16 +3,25 @@
# Help: Searches for apps that are available to install. # Help: Searches for apps that are available to install.
# #
# If used with [query], shows app names that match the query. # If used with [query], shows app names that match the query.
# - With 'use_sqlite_cache' enabled, [query] is partially matched against app names, binaries, and shortcuts.
# - Without 'use_sqlite_cache', [query] can be a regular expression to match against app names and binaries.
# Without [query], shows all the available apps. # Without [query], shows all the available apps.
param($query) param($query)
. "$PSScriptRoot\..\lib\manifest.ps1" # 'manifest' . "$PSScriptRoot\..\lib\manifest.ps1" # 'manifest'
. "$PSScriptRoot\..\lib\versions.ps1" # 'Get-LatestVersion' . "$PSScriptRoot\..\lib\versions.ps1" # 'Get-LatestVersion'
. "$PSScriptRoot\..\lib\download.ps1"
$list = [System.Collections.Generic.List[PSCustomObject]]::new() $list = @()
try {
$query = New-Object Regex $query, 'IgnoreCase'
} catch {
abort "Invalid regular expression: $($_.Exception.InnerException.Message)"
}
$githubtoken = Get-GitHubToken
$authheader = @{}
if ($githubtoken) {
$authheader = @{'Authorization' = "token $githubtoken"}
}
function bin_match($manifest, $query) { function bin_match($manifest, $query) {
if (!$manifest.bin) { return $false } if (!$manifest.bin) { return $false }
@@ -23,98 +32,41 @@ function bin_match($manifest, $query) {
if ((strip_ext $fname) -match $query) { $fname } if ((strip_ext $fname) -match $query) { $fname }
elseif ($alias -match $query) { $alias } elseif ($alias -match $query) { $alias }
} }
if ($bins) { return $bins }
else { return $false }
}
function bin_match_json($json, $query) {
[System.Text.Json.JsonElement]$bin = [System.Text.Json.JsonElement]::new()
if (!$json.RootElement.TryGetProperty('bin', [ref] $bin)) { return $false }
$bins = @()
if ($bin.ValueKind -eq [System.Text.Json.JsonValueKind]::String -and [System.IO.Path]::GetFileNameWithoutExtension($bin) -match $query) {
$bins += [System.IO.Path]::GetFileName($bin)
} elseif ($bin.ValueKind -eq [System.Text.Json.JsonValueKind]::Array) {
foreach ($subbin in $bin.EnumerateArray()) {
if ($subbin.ValueKind -eq [System.Text.Json.JsonValueKind]::String -and [System.IO.Path]::GetFileNameWithoutExtension($subbin) -match $query) {
$bins += [System.IO.Path]::GetFileName($subbin)
} elseif ($subbin.ValueKind -eq [System.Text.Json.JsonValueKind]::Array) {
if ([System.IO.Path]::GetFileNameWithoutExtension($subbin[0]) -match $query) {
$bins += [System.IO.Path]::GetFileName($subbin[0])
} elseif ($subbin.GetArrayLength() -ge 2 -and $subbin[1] -match $query) {
$bins += $subbin[1]
}
}
}
}
if ($bins) { return $bins } if ($bins) { return $bins }
else { return $false } else { return $false }
} }
function search_bucket($bucket, $query) { function search_bucket($bucket, $query) {
$apps = Get-ChildItem (Find-BucketDirectory $bucket) -Filter '*.json' -Recurse $apps = apps_in_bucket (Find-BucketDirectory $bucket) | ForEach-Object { @{ name = $_ } }
$apps | ForEach-Object { if ($query) {
$filepath = $_.FullName $apps = $apps | Where-Object {
if ($_.name -match $query) { return $true }
$json = try { $bin = bin_match (manifest $_.name $bucket) $query
[System.Text.Json.JsonDocument]::Parse([System.IO.File]::ReadAllText($filepath))
} catch {
debug "Failed to parse manifest file: $filepath (error: $_)"
return
}
$name = $_.BaseName
if ($name -match $query) {
$list.Add([PSCustomObject]@{
Name = $name
Version = $json.RootElement.GetProperty('version')
Source = $bucket
Binaries = ''
})
} else {
$bin = bin_match_json $json $query
if ($bin) { if ($bin) {
$list.Add([PSCustomObject]@{ $_.bin = $bin
Name = $name return $true
Version = $json.RootElement.GetProperty('version')
Source = $bucket
Binaries = $bin -join ' | '
})
} }
} }
} }
$apps | ForEach-Object { $_.version = (Get-LatestVersion -AppName $_.name -Bucket $bucket); $_ }
} }
# fallback function for PowerShell 5 function download_json($url) {
function search_bucket_legacy($bucket, $query) { $ProgressPreference = 'SilentlyContinue'
$apps = Get-ChildItem (Find-BucketDirectory $bucket) -Filter '*.json' -Recurse $result = Invoke-WebRequest $url -UseBasicParsing -Headers $authheader | Select-Object -ExpandProperty content | ConvertFrom-Json
$ProgressPreference = 'Continue'
$result
}
$apps | ForEach-Object { function github_ratelimit_reached {
$manifest = [System.IO.File]::ReadAllText($_.FullName) | ConvertFrom-Json -ErrorAction Continue $api_link = 'https://api.github.com/rate_limit'
$name = $_.BaseName $ret = (download_json $api_link).rate.remaining -eq 0
if ($ret) {
if ($name -match $query) { Write-Host "GitHub API rate limit reached.
$list.Add([PSCustomObject]@{ Please try again later or configure your API token using 'scoop config gh_token <your token>'."
Name = $name
Version = $manifest.Version
Source = $bucket
Binaries = ''
})
} else {
$bin = bin_match $manifest $query
if ($bin) {
$list.Add([PSCustomObject]@{
Name = $name
Version = $manifest.Version
Source = $bucket
Binaries = $bin -join ' | '
})
}
}
} }
$ret
} }
function search_remote($bucket, $query) { function search_remote($bucket, $query) {
@@ -136,7 +88,7 @@ function search_remotes($query) {
$names = $buckets | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty name $names = $buckets | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty name
$results = $names | Where-Object { !(Test-Path $(Find-BucketDirectory $_)) } | ForEach-Object { $results = $names | Where-Object { !(Test-Path $(Find-BucketDirectory $_)) } | ForEach-Object {
@{ 'bucket' = $_; 'results' = (search_remote $_ $query) } @{ "bucket" = $_; "results" = (search_remote $_ $query) }
} | Where-Object { $_.results } } | Where-Object { $_.results }
if ($results.count -gt 0) { if ($results.count -gt 0) {
@@ -144,58 +96,46 @@ function search_remotes($query) {
(add them using 'scoop bucket add <bucket name>')" (add them using 'scoop bucket add <bucket name>')"
} }
$remote_list = @()
$results | ForEach-Object { $results | ForEach-Object {
$bucket = $_.bucket $name = $_.bucket
$_.results | ForEach-Object { $_.results | ForEach-Object {
$item = [ordered]@{} $item = [ordered]@{}
$item.Name = $_ $item.Name = $_
$item.Source = $bucket $item.Source = $name
$remote_list += [PSCustomObject]$item $list += [PSCustomObject]$item
} }
} }
$remote_list
}
if (get_config USE_SQLITE_CACHE) {
. "$PSScriptRoot\..\lib\database.ps1"
Select-ScoopDBItem $query -From @('name', 'binary', 'shortcut') |
Select-Object -Property name, version, bucket, binary |
ForEach-Object {
$list.Add([PSCustomObject]@{
Name = $_.name
Version = $_.version
Source = $_.bucket
Binaries = $_.binary
})
}
} else {
try {
$query = New-Object Regex $query, 'IgnoreCase'
} catch {
abort "Invalid regular expression: $($_.Exception.InnerException.Message)"
}
$jsonTextAvailable = [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { [System.IO.Path]::GetFileNameWithoutExtension($_.Location) -eq 'System.Text.Json' }
Get-LocalBucket | ForEach-Object {
if ($jsonTextAvailable) {
search_bucket $_ $query
} else {
search_bucket_legacy $_ $query
}
}
}
if ($list.Count -gt 0) {
Write-Host 'Results from local buckets...'
$list $list
} }
if ($list.Count -eq 0 -and !(github_ratelimit_reached)) { Get-LocalBucket | ForEach-Object {
$res = search_bucket $_ $query
$local_results = $local_results -or $res
if ($res) {
$name = "$_"
$res | ForEach-Object {
$item = [ordered]@{}
$item.Name = $_.name
$item.Version = $_.version
$item.Source = $name
$item.Binaries = ""
if ($_.bin) { $item.Binaries = $_.bin -join ' | ' }
$list += [PSCustomObject]$item
}
}
}
if ($list.Length -gt 0) {
Write-Host "Results from local buckets..."
$list
}
if (!$local_results -and !(github_ratelimit_reached)) {
$remote_results = search_remotes $query $remote_results = search_remotes $query
if (!$remote_results) { if (!$remote_results) {
warn 'No matches found.' warn "No matches found."
exit 1 exit 1
} }
$remote_results $remote_results

View File

@@ -12,7 +12,7 @@
# #
# To list all shims or matching shims, use the 'list' subcommand: # To list all shims or matching shims, use the 'list' subcommand:
# #
# scoop shim list [<regex_pattern>...] # scoop shim list [<shim_name>/<pattern>...]
# #
# To show a shim's information, use the 'info' subcommand: # To show a shim's information, use the 'info' subcommand:
# #
@@ -35,7 +35,6 @@ param($SubCommand)
. "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\getopt.ps1"
. "$PSScriptRoot\..\lib\install.ps1" # for rm_shim . "$PSScriptRoot\..\lib\install.ps1" # for rm_shim
. "$PSScriptRoot\..\lib\system.ps1" # 'Add-Path' (indirectly)
if ($SubCommand -notin @('add', 'rm', 'list', 'info', 'alter')) { if ($SubCommand -notin @('add', 'rm', 'list', 'info', 'alter')) {
if (!$SubCommand) { if (!$SubCommand) {
@@ -83,7 +82,7 @@ function Get-ShimInfo($ShimPath) {
function Get-ShimPath($ShimName, $Global) { function Get-ShimPath($ShimName, $Global) {
'.shim', '.ps1' | ForEach-Object { '.shim', '.ps1' | ForEach-Object {
$shimPath = Join-Path (shimdir $Global) "$ShimName$_" $shimPath = Join-Path (shimdir $Global) "$ShimName$_"
if (Test-Path -LiteralPath $shimPath) { if (Test-Path $shimPath) {
return $shimPath return $shimPath
} }
} }
@@ -145,7 +144,7 @@ switch ($SubCommand) {
$other | ForEach-Object { $other | ForEach-Object {
try { try {
$pattern = $_ $pattern = $_
[void][Regex]::New($pattern) [Regex]::New($pattern)
} catch { } catch {
Write-Host "ERROR: Invalid pattern: " -ForegroundColor Red -NoNewline Write-Host "ERROR: Invalid pattern: " -ForegroundColor Red -NoNewline
Write-Host $pattern -ForegroundColor Magenta Write-Host $pattern -ForegroundColor Magenta

View File

@@ -6,10 +6,9 @@
. "$PSScriptRoot\..\lib\manifest.ps1" # 'manifest' 'parse_json' "install_info" . "$PSScriptRoot\..\lib\manifest.ps1" # 'manifest' 'parse_json' "install_info"
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion' . "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
. "$PSScriptRoot\..\lib\download.ps1" # 'Get-UserAgent'
# check if scoop needs updating # check if scoop needs updating
$currentdir = versiondir 'scoop' 'current' $currentdir = fullpath $(versiondir 'scoop' 'current')
$needs_update = $false $needs_update = $false
$bucket_needs_update = $false $bucket_needs_update = $false
$script:network_failure = $false $script:network_failure = $false
@@ -59,7 +58,7 @@ $true, $false | ForEach-Object { # local and global apps
Get-ChildItem $dir | Where-Object name -NE 'scoop' | ForEach-Object { Get-ChildItem $dir | Where-Object name -NE 'scoop' | ForEach-Object {
$app = $_.name $app = $_.name
$status = app_status $app $global $status = app_status $app $global
if (!$status.outdated -and !$status.failed -and !$status.deprecated -and !$status.removed -and !$status.missing_deps) { return } if (!$status.outdated -and !$status.failed -and !$status.removed -and !$status.missing_deps) { return }
$item = [ordered]@{} $item = [ordered]@{}
$item.Name = $app $item.Name = $app
@@ -69,7 +68,6 @@ $true, $false | ForEach-Object { # local and global apps
$info = @() $info = @()
if ($status.failed) { $info += 'Install failed' } if ($status.failed) { $info += 'Install failed' }
if ($status.hold) { $info += 'Held package' } if ($status.hold) { $info += 'Held package' }
if ($status.deprecated) { $info += 'Deprecated' }
if ($status.removed) { $info += 'Manifest removed' } if ($status.removed) { $info += 'Manifest removed' }
$item.Info = $info -join ', ' $item.Info = $info -join ', '
$list += [PSCustomObject]$item $list += [PSCustomObject]$item

View File

@@ -53,16 +53,8 @@ $apps | ForEach-Object {
} }
$dir = versiondir $app $version $global $dir = versiondir $app $version $global
$json = install_info $app $version $global $json = install_info $app $version $global
if (!$json) {
error "Failed to unhold '$app'"
continue
}
$install = @{} $install = @{}
$json | Get-Member -MemberType Properties | ForEach-Object { $install.Add($_.Name, $json.($_.Name)) } $json | Get-Member -MemberType Properties | ForEach-Object { $install.Add($_.Name, $json.($_.Name)) }
if (!$install.hold) {
info "'$app' is not held."
continue
}
$install.hold = $null $install.hold = $null
save_install_info $install $dir save_install_info $install $dir
success "$app is no longer held and can be updated again." success "$app is no longer held and can be updated again."

View File

@@ -8,7 +8,6 @@
. "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\getopt.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' 'Select-CurrentVersion' (indirectly) . "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' 'Select-CurrentVersion' (indirectly)
. "$PSScriptRoot\..\lib\system.ps1"
. "$PSScriptRoot\..\lib\install.ps1" . "$PSScriptRoot\..\lib\install.ps1"
. "$PSScriptRoot\..\lib\shortcuts.ps1" . "$PSScriptRoot\..\lib\shortcuts.ps1"
. "$PSScriptRoot\..\lib\psmodules.ps1" . "$PSScriptRoot\..\lib\psmodules.ps1"
@@ -74,7 +73,7 @@ if (!$apps) { exit 0 }
continue continue
} }
Invoke-Installer -Path $dir -Manifest $manifest -ProcessorArchitecture $architecture -Global $global -Uninstall run_uninstaller $manifest $architecture $dir
rm_shims $app $manifest $global $architecture rm_shims $app $manifest $global $architecture
rm_startmenu_shortcuts $manifest $global $architecture rm_startmenu_shortcuts $manifest $global $architecture

View File

@@ -10,13 +10,12 @@
# -g, --global Update a globally installed app # -g, --global Update a globally installed app
# -i, --independent Don't install dependencies automatically # -i, --independent Don't install dependencies automatically
# -k, --no-cache Don't use the download cache # -k, --no-cache Don't use the download cache
# -s, --skip-hash-check Skip hash validation (use with caution!) # -s, --skip Skip hash validation (use with caution!)
# -q, --quiet Hide extraneous messages # -q, --quiet Hide extraneous messages
# -a, --all Update all apps (alternative to '*') # -a, --all Update all apps (alternative to '*')
. "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\getopt.ps1"
. "$PSScriptRoot\..\lib\json.ps1" # 'save_install_info' in 'manifest.ps1' (indirectly) . "$PSScriptRoot\..\lib\json.ps1" # 'save_install_info' in 'manifest.ps1' (indirectly)
. "$PSScriptRoot\..\lib\system.ps1"
. "$PSScriptRoot\..\lib\shortcuts.ps1" . "$PSScriptRoot\..\lib\shortcuts.ps1"
. "$PSScriptRoot\..\lib\psmodules.ps1" . "$PSScriptRoot\..\lib\psmodules.ps1"
. "$PSScriptRoot\..\lib\decompress.ps1" . "$PSScriptRoot\..\lib\decompress.ps1"
@@ -24,16 +23,12 @@
. "$PSScriptRoot\..\lib\versions.ps1" . "$PSScriptRoot\..\lib\versions.ps1"
. "$PSScriptRoot\..\lib\depends.ps1" . "$PSScriptRoot\..\lib\depends.ps1"
. "$PSScriptRoot\..\lib\install.ps1" . "$PSScriptRoot\..\lib\install.ps1"
. "$PSScriptRoot\..\lib\download.ps1"
if (get_config USE_SQLITE_CACHE) {
. "$PSScriptRoot\..\lib\database.ps1"
}
$opt, $apps, $err = getopt $args 'gfiksqa' 'global', 'force', 'independent', 'no-cache', 'skip-hash-check', 'quiet', 'all' $opt, $apps, $err = getopt $args 'gfiksqa' 'global', 'force', 'independent', 'no-cache', 'skip', 'quiet', 'all'
if ($err) { "scoop update: $err"; exit 1 } if ($err) { "scoop update: $err"; exit 1 }
$global = $opt.g -or $opt.global $global = $opt.g -or $opt.global
$force = $opt.f -or $opt.force $force = $opt.f -or $opt.force
$check_hash = !($opt.s -or $opt.'skip-hash-check') $check_hash = !($opt.s -or $opt.skip)
$use_cache = !($opt.k -or $opt.'no-cache') $use_cache = !($opt.k -or $opt.'no-cache')
$quiet = $opt.q -or $opt.quiet $quiet = $opt.q -or $opt.quiet
$independent = $opt.i -or $opt.independent $independent = $opt.i -or $opt.independent
@@ -42,21 +37,21 @@ $all = $opt.a -or $opt.all
# load config # load config
$configRepo = get_config SCOOP_REPO $configRepo = get_config SCOOP_REPO
if (!$configRepo) { if (!$configRepo) {
$configRepo = 'https://github.com/ScoopInstaller/Scoop' $configRepo = "https://github.com/ScoopInstaller/Scoop"
set_config SCOOP_REPO $configRepo | Out-Null set_config SCOOP_REPO $configRepo | Out-Null
} }
# Find current update channel from config # Find current update channel from config
$configBranch = get_config SCOOP_BRANCH $configBranch = get_config SCOOP_BRANCH
if (!$configBranch) { if (!$configBranch) {
$configBranch = 'master' $configBranch = "master"
set_config SCOOP_BRANCH $configBranch | Out-Null set_config SCOOP_BRANCH $configBranch | Out-Null
} }
if (($PSVersionTable.PSVersion.Major) -lt 5) { if(($PSVersionTable.PSVersion.Major) -lt 5) {
# check powershell version # check powershell version
Write-Output 'PowerShell 5 or later is required to run Scoop.' Write-Output "PowerShell 5 or later is required to run Scoop."
Write-Output 'Upgrade PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows' Write-Output "Upgrade PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows"
break break
} }
$show_update_log = get_config SHOW_UPDATE_LOG $true $show_update_log = get_config SHOW_UPDATE_LOG $true
@@ -67,15 +62,15 @@ function Sync-Scoop {
[Switch]$Log [Switch]$Log
) )
# Test if Scoop Core is hold # Test if Scoop Core is hold
if (Test-ScoopCoreOnHold) { if(Test-ScoopCoreOnHold) {
return return
} }
# check for git # check for git
if (!(Test-GitAvailable)) { abort "Scoop uses Git to update itself. Run 'scoop install git' and try again." } if (!(Test-GitAvailable)) { abort "Scoop uses Git to update itself. Run 'scoop install git' and try again." }
Write-Host 'Updating Scoop...' Write-Host "Updating Scoop..."
$currentdir = versiondir 'scoop' 'current' $currentdir = fullpath $(versiondir 'scoop' 'current')
if (!(Test-Path "$currentdir\.git")) { if (!(Test-Path "$currentdir\.git")) {
$newdir = "$currentdir\..\new" $newdir = "$currentdir\..\new"
$olddir = "$currentdir\..\old" $olddir = "$currentdir\..\old"
@@ -94,7 +89,7 @@ function Sync-Scoop {
Rename-Item $newdir 'current' -ErrorAction Stop Rename-Item $newdir 'current' -ErrorAction Stop
} catch { } catch {
Write-Warning $_ Write-Warning $_
abort "Scoop update failed. Folder in use. Please rename folders $currentdir to ``old`` and $newdir to ``current``." abort "Scoop update failed. Folder in use. Paste $newdir into $currentdir."
} }
} }
} else { } else {
@@ -112,10 +107,10 @@ function Sync-Scoop {
# Stash uncommitted changes # Stash uncommitted changes
if (Invoke-Git -Path $currentdir -ArgumentList @('diff', 'HEAD', '--name-only')) { if (Invoke-Git -Path $currentdir -ArgumentList @('diff', 'HEAD', '--name-only')) {
if (get_config AUTOSTASH_ON_CONFLICT) { if (get_config AUTOSTASH_ON_CONFLICT) {
warn 'Uncommitted changes detected. Stashing...' warn "Uncommitted changes detected. Stashing..."
Invoke-Git -Path $currentdir -ArgumentList @('stash', 'push', '-m', "WIP at $([System.DateTime]::Now.ToString('o'))", '-u', '-q') Invoke-Git -Path $currentdir -ArgumentList @('stash', 'push', '-m', "WIP at $([System.DateTime]::Now.ToString('o'))", '-u', '-q')
} else { } else {
warn 'Uncommitted changes detected. Update aborted.' warn "Uncommitted changes detected. Update aborted."
return return
} }
} }
@@ -156,7 +151,7 @@ function Sync-Bucket {
Param ( Param (
[Switch]$Log [Switch]$Log
) )
Write-Host 'Updating Buckets...' Write-Host "Updating Buckets..."
if (!(Test-Path (Join-Path (Find-BucketDirectory 'main' -Root) '.git'))) { if (!(Test-Path (Join-Path (Find-BucketDirectory 'main' -Root) '.git'))) {
info "Converting 'main' bucket to git repo..." info "Converting 'main' bucket to git repo..."
@@ -182,79 +177,31 @@ function Sync-Bucket {
$buckets | Where-Object { !$_.valid } | ForEach-Object { Write-Host "'$($_.name)' is not a git repository. Skipped." } $buckets | Where-Object { !$_.valid } | ForEach-Object { Write-Host "'$($_.name)' is not a git repository. Skipped." }
$updatedFiles = [System.Collections.ArrayList]::Synchronized([System.Collections.ArrayList]::new())
$removedFiles = [System.Collections.ArrayList]::Synchronized([System.Collections.ArrayList]::new())
if ($PSVersionTable.PSVersion.Major -ge 7) { if ($PSVersionTable.PSVersion.Major -ge 7) {
# Parallel parameter is available since PowerShell 7 # Parallel parameter is available since PowerShell 7
$buckets | Where-Object { $_.valid } | ForEach-Object -ThrottleLimit 5 -Parallel { $buckets | Where-Object { $_.valid } | ForEach-Object -ThrottleLimit 5 -Parallel {
. "$using:PSScriptRoot\..\lib\core.ps1" . "$using:PSScriptRoot\..\lib\core.ps1"
. "$using:PSScriptRoot\..\lib\buckets.ps1"
$name = $_.name
$bucketLoc = $_.path $bucketLoc = $_.path
$innerBucketLoc = Find-BucketDirectory $name $name = $_.name
$previousCommit = Invoke-Git -Path $bucketLoc -ArgumentList @('rev-parse', 'HEAD') $previousCommit = Invoke-Git -Path $bucketLoc -ArgumentList @('rev-parse', 'HEAD')
Invoke-Git -Path $bucketLoc -ArgumentList @('pull', '-q') Invoke-Git -Path $bucketLoc -ArgumentList @('pull', '-q')
if ($using:Log) { if ($using:Log) {
Invoke-GitLog -Path $bucketLoc -Name $name -CommitHash $previousCommit Invoke-GitLog -Path $bucketLoc -Name $name -CommitHash $previousCommit
} }
if (get_config USE_SQLITE_CACHE) {
Invoke-Git -Path $bucketLoc -ArgumentList @('diff', '--name-status', $previousCommit) | ForEach-Object {
$status, $file = $_ -split '\s+', 2
$filePath = Join-Path $bucketLoc $file
if ($filePath -match "^$([regex]::Escape($innerBucketLoc)).*\.json$") {
switch ($status) {
{ $_ -in 'A', 'M', 'R' } {
[void]($using:updatedFiles).Add($filePath)
}
'D' {
[void]($using:removedFiles).Add([pscustomobject]@{
Name = ([System.IO.FileInfo]$file).BaseName
Bucket = $name
})
}
}
}
}
}
} }
} else { } else {
$buckets | Where-Object { $_.valid } | ForEach-Object { $buckets | Where-Object { $_.valid } | ForEach-Object {
$name = $_.name
$bucketLoc = $_.path $bucketLoc = $_.path
$innerBucketLoc = Find-BucketDirectory $name $name = $_.name
$previousCommit = Invoke-Git -Path $bucketLoc -ArgumentList @('rev-parse', 'HEAD') $previousCommit = Invoke-Git -Path $bucketLoc -ArgumentList @('rev-parse', 'HEAD')
Invoke-Git -Path $bucketLoc -ArgumentList @('pull', '-q') Invoke-Git -Path $bucketLoc -ArgumentList @('pull', '-q')
if ($Log) { if ($Log) {
Invoke-GitLog -Path $bucketLoc -Name $name -CommitHash $previousCommit Invoke-GitLog -Path $bucketLoc -Name $name -CommitHash $previousCommit
} }
if (get_config USE_SQLITE_CACHE) {
Invoke-Git -Path $bucketLoc -ArgumentList @('diff', '--name-status', $previousCommit) | ForEach-Object {
$status, $file = $_ -split '\s+', 2
$filePath = Join-Path $bucketLoc $file
if ($filePath -match "^$([regex]::Escape($innerBucketLoc)).*\.json$") {
switch ($status) {
{ $_ -in 'A', 'M', 'R' } {
[void]($updatedFiles).Add($filePath)
} }
'D' {
[void]($removedFiles).Add([pscustomobject]@{
Name = ([System.IO.FileInfo]$file).BaseName
Bucket = $name
})
}
}
}
}
}
}
}
if ((get_config USE_SQLITE_CACHE) -and ($updatedFiles.Count -gt 0 -or $removedFiles.Count -gt 0)) {
info 'Updating cache...'
Set-ScoopDB -Path $updatedFiles
$removedFiles | Remove-ScoopDBItem
} }
} }
@@ -293,17 +240,10 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c
Write-Host "Updating '$app' ($old_version -> $version)" Write-Host "Updating '$app' ($old_version -> $version)"
#region Workaround for #2952
if (test_running_process $app $global) {
Write-Host 'Running process detected, skip updating.'
return
}
#endregion Workaround for #2952
# region Workaround # region Workaround
# Workaround for https://github.com/ScoopInstaller/Scoop/issues/2220 until install is refactored # Workaround for https://github.com/ScoopInstaller/Scoop/issues/2220 until install is refactored
# Remove and replace whole region after proper fix # Remove and replace whole region after proper fix
Write-Host 'Downloading new version' Write-Host "Downloading new version"
if (Test-Aria2Enabled) { if (Test-Aria2Enabled) {
Invoke-CachedAria2Download $app $version $manifest $architecture $cachedir $manifest.cookie $true $check_hash Invoke-CachedAria2Download $app $version $manifest $architecture $cachedir $manifest.cookie $true $check_hash
} else { } else {
@@ -314,19 +254,19 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c
if ($check_hash) { if ($check_hash) {
$manifest_hash = hash_for_url $manifest $url $architecture $manifest_hash = hash_for_url $manifest $url $architecture
$source = cache_path $app $version $url $source = fullpath (cache_path $app $version $url)
$ok, $err = check_hash $source $manifest_hash $(show_app $app $bucket) $ok, $err = check_hash $source $manifest_hash $(show_app $app $bucket)
if (!$ok) { if (!$ok) {
error $err error $err
if (Test-Path $source) { if (Test-Path $source) {
# rm cached file # rm cached file
Remove-Item -Force $source Remove-Item -force $source
} }
if ($url.Contains('sourceforge.net')) { if ($url.Contains('sourceforge.net')) {
Write-Host -f yellow 'SourceForge.net is known for causing hash validation fails. Please try again before opening a ticket.' Write-Host -f yellow 'SourceForge.net is known for causing hash validation fails. Please try again before opening a ticket.'
} }
abort $(new_issue_msg $app $bucket 'hash check failed') abort $(new_issue_msg $app $bucket "hash check failed")
} }
} }
} }
@@ -340,17 +280,24 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c
Invoke-HookScript -HookType 'pre_uninstall' -Manifest $old_manifest -Arch $architecture Invoke-HookScript -HookType 'pre_uninstall' -Manifest $old_manifest -Arch $architecture
#region Workaround for #2952
if (test_running_process $app $global) {
return
}
#endregion Workaround for #2952
Write-Host "Uninstalling '$app' ($old_version)" Write-Host "Uninstalling '$app' ($old_version)"
Invoke-Installer -Path $dir -Manifest $old_manifest -ProcessorArchitecture $architecture -Uninstall run_uninstaller $old_manifest $architecture $dir
rm_shims $app $old_manifest $global $architecture rm_shims $app $old_manifest $global $architecture
env_rm_path $old_manifest $dir $global $architecture
env_rm $old_manifest $global $architecture
# If a junction was used during install, that will have been used # If a junction was used during install, that will have been used
# as the reference directory. Otherwise it will just be the version # as the reference directory. Otherwise it will just be the version
# directory. # directory.
$refdir = unlink_current $dir $refdir = unlink_current $dir
uninstall_psmodule $old_manifest $refdir $global uninstall_psmodule $old_manifest $refdir $global
env_rm_path $old_manifest $refdir $global $architecture
env_rm $old_manifest $global $architecture
if ($force -and ($old_version -eq $version)) { if ($force -and ($old_version -eq $version)) {
if (!(Test-Path "$dir/../_$version.old")) { if (!(Test-Path "$dir/../_$version.old")) {
@@ -456,11 +403,11 @@ if (-not ($apps -or $all)) {
} elseif ($outdated.Length -eq 0) { } elseif ($outdated.Length -eq 0) {
Write-Host -f Green "Latest versions for all apps are installed! For more information try 'scoop status'" Write-Host -f Green "Latest versions for all apps are installed! For more information try 'scoop status'"
} else { } else {
Write-Host -f DarkCyan 'Updating one outdated app:' Write-Host -f DarkCyan "Updating one outdated app:"
} }
} }
$suggested = @{} $suggested = @{};
# $outdated is a list of ($app, $global) tuples # $outdated is a list of ($app, $global) tuples
$outdated | ForEach-Object { update @_ $quiet $independent $suggested $use_cache $check_hash } $outdated | ForEach-Object { update @_ $quiet $independent $suggested $use_cache $check_hash }
} }

View File

@@ -29,28 +29,29 @@
# -p, --passthru Return reports as objects # -p, --passthru Return reports as objects
. "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\getopt.ps1"
. "$PSScriptRoot\..\lib\versions.ps1" # 'Select-CurrentVersion'
. "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest' . "$PSScriptRoot\..\lib\manifest.ps1" # 'Get-Manifest'
. "$PSScriptRoot\..\lib\json.ps1" # 'json_path' . "$PSScriptRoot\..\lib\json.ps1" # 'json_path'
. "$PSScriptRoot\..\lib\download.ps1" # 'hash_for_url' . "$PSScriptRoot\..\lib\install.ps1" # 'hash_for_url'
. "$PSScriptRoot\..\lib\depends.ps1" # 'Get-Dependency' . "$PSScriptRoot\..\lib\depends.ps1" # 'Get-Dependency'
$opt, $apps, $err = getopt $args 'asnup' @('all', 'scan', 'no-depends', 'no-update-scoop', 'passthru') $opt, $apps, $err = getopt $args 'asnup' @('all', 'scan', 'no-depends', 'no-update-scoop', 'passthru')
if ($err) { "scoop virustotal: $err"; exit 1 } if ($err) { "scoop virustotal: $err"; exit 1 }
$all = $apps -eq '*' -or $opt.a -or $opt.all if (!$apps) { my_usage; exit 1 }
if (!$apps -and !$all) { my_usage; exit 1 } $architecture = Format-ArchitectureString
$architecture = Get-DefaultArchitecture
if (is_scoop_outdated) { if (is_scoop_outdated) {
if ($opt.u -or $opt.'no-update-scoop') { if ($opt.u -or $opt.'no-update-scoop') {
warn 'Scoop is out of date.' warn 'Scoop is out of date.'
} else { } else {
& "$PSScriptRoot\scoop-update.ps1" scoop update
} }
} }
if ($all) { $apps_param = $apps
$apps = (installed_apps $false) + (installed_apps $true)
if ($apps_param -eq '*' -or $opt.a -or $opt.all) {
$apps = installed_apps $false
$apps += installed_apps $true
} }
if (!$opt.n -and !$opt.'no-depends') { if (!$opt.n -and !$opt.'no-depends') {
@@ -87,6 +88,11 @@ Function ConvertTo-VirusTotalUrlId ($url) {
$url_id $url_id
} }
Function Get-RemoteFileSize ($url) {
$response = Invoke-WebRequest -Uri $url -Method HEAD -UseBasicParsing
$response.Headers.'Content-Length' | ForEach-Object { [System.Convert]::ToInt32($_) }
}
Function Get-VirusTotalResultByHash ($hash, $url, $app) { Function Get-VirusTotalResultByHash ($hash, $url, $app) {
$hash = $hash.ToLower() $hash = $hash.ToLower()
$api_url = "https://www.virustotal.com/api/v3/files/$hash" $api_url = "https://www.virustotal.com/api/v3/files/$hash"
@@ -130,7 +136,7 @@ Function Get-VirusTotalResultByHash ($hash, $url, $app) {
warn "$app`: $unsafe/$total, see $report_url" warn "$app`: $unsafe/$total, see $report_url"
} }
Default { Default {
warn "$([char]0x1b)[31m$app`: $unsafe/$total, see $report_url$([char]0x1b)[0m" warn "`e[31m$app`: $unsafe/$total, see $report_url`e[0m"
} }
} }
$maliciousResults = $vendorResults | $maliciousResults = $vendorResults |

View File

@@ -4,7 +4,7 @@
param($command) param($command)
if (!$command) { if (!$command) {
error '<command> missing' 'ERROR: <command> missing'
my_usage my_usage
exit 1 exit 1
} }
@@ -12,7 +12,7 @@ if (!$command) {
$path = Get-CommandPath $command $path = Get-CommandPath $command
if ($null -eq $path) { if ($null -eq $path) {
warn "'$command' not found, not a scoop shim, or a broken shim." Write-Host "'$command' not found / not a scoop shim."
exit 2 exit 2
} else { } else {
friendly_path $path friendly_path $path

View File

@@ -127,6 +127,10 @@
"installer": { "installer": {
"$ref": "#/definitions/installer" "$ref": "#/definitions/installer"
}, },
"msi": {
"$ref": "#/definitions/stringOrArrayOfStrings",
"description": "Deprecated"
},
"post_install": { "post_install": {
"$ref": "#/definitions/stringOrArrayOfStrings" "$ref": "#/definitions/stringOrArrayOfStrings"
}, },
@@ -599,6 +603,10 @@
"license": { "license": {
"$ref": "#/definitions/license" "$ref": "#/definitions/license"
}, },
"msi": {
"$ref": "#/definitions/stringOrArrayOfStrings",
"description": "Deprecated"
},
"notes": { "notes": {
"$ref": "#/definitions/stringOrArrayOfStrings" "$ref": "#/definitions/stringOrArrayOfStrings"
}, },

1
supporting/shimexe/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
packages/

View File

@@ -0,0 +1 @@
9726c3a429009a5b22bd92cb8ab96724c670e164e7240e83f27b7c8b7bd1ca39 *shim.exe

View File

@@ -0,0 +1 @@
18a737674afde4d5e7e1647d8d1e98471bb260513c57739651f92fdf1647d76c92f0cd0a9bb458daf4eae4bdab9d31404162acf6d74a041e6415752b75d722e0 *shim.exe

Binary file not shown.

View File

@@ -0,0 +1,22 @@
Param([Switch]$Fast)
Push-Location $PSScriptRoot
. "$PSScriptRoot\..\..\lib\core.ps1"
. "$PSScriptRoot\..\..\lib\install.ps1"
if (!$Fast) {
Write-Host "Install dependencies ..."
& "$PSScriptRoot\install.ps1"
}
$output = "$PSScriptRoot\bin"
Write-Output 'Compiling shim.cs ...'
& "$PSScriptRoot\packages\Microsoft.Net.Compilers.Toolset\tasks\net472\csc.exe" -deterministic -platform:anycpu -nologo -optimize -target:exe -out:"$output\shim.exe" shim.cs
Write-Output 'Computing checksums ...'
Remove-Item "$PSScriptRoot\bin\checksum.sha256" -ErrorAction Ignore
Remove-Item "$PSScriptRoot\bin\checksum.sha512" -ErrorAction Ignore
Get-ChildItem "$PSScriptRoot\bin\*" -Include *.exe, *.dll | ForEach-Object {
"$((Get-FileHash -Path $_ -Algorithm SHA256).Hash.ToLower()) *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha256" -Append -Encoding oem
"$((Get-FileHash -Path $_ -Algorithm SHA512).Hash.ToLower()) *$($_.Name)" | Out-File "$PSScriptRoot\bin\checksum.sha512" -Append -Encoding oem
}
Pop-Location

View File

@@ -0,0 +1,8 @@
# https://github.com/edymtt/nugetstandalone
$destinationFolder = "$PSScriptRoot\packages"
if ((Test-Path -Path $destinationFolder)) {
Remove-Item -Path $destinationFolder -Recurse | Out-Null
}
New-Item $destinationFolder -Type Directory | Out-Null
nuget install packages.config -o $destinationFolder -ExcludeVersion

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Net.Compilers.Toolset" version="4.2.0" targetFramework="net45" developmentDependency="true" />
</packages>

168
supporting/shimexe/shim.cs Normal file
View File

@@ -0,0 +1,168 @@
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace Scoop {
class Program {
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
static extern bool CreateProcess(string lpApplicationName,
string lpCommandLine, IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes, bool bInheritHandles,
uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
[In] ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
const int ERROR_ELEVATION_REQUIRED = 740;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFO {
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION {
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[DllImport("kernel32.dll", SetLastError=true)]
static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
const UInt32 INFINITE = 0xFFFFFFFF;
[DllImport("kernel32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
static int Main(string[] args) {
var exe = Assembly.GetExecutingAssembly().Location;
var dir = Path.GetDirectoryName(exe);
var name = Path.GetFileNameWithoutExtension(exe);
var configPath = Path.Combine(dir, name + ".shim");
if(!File.Exists(configPath)) {
Console.Error.WriteLine("Couldn't find " + Path.GetFileName(configPath) + " in " + dir);
return 1;
}
var config = Config(configPath);
var path = Get(config, "path");
var add_args = Get(config, "args");
var si = new STARTUPINFO();
var pi = new PROCESS_INFORMATION();
// create command line
var cmd_args = add_args ?? "";
var pass_args = GetArgs(Environment.CommandLine);
if(!string.IsNullOrEmpty(pass_args)) {
if(!string.IsNullOrEmpty(cmd_args)) cmd_args += " ";
cmd_args += pass_args;
}
if(!string.IsNullOrEmpty(cmd_args)) cmd_args = " " + cmd_args;
var cmd = "\"" + path + "\"" + cmd_args;
if(!CreateProcess(null, cmd, IntPtr.Zero, IntPtr.Zero,
bInheritHandles: true,
dwCreationFlags: 0,
lpEnvironment: IntPtr.Zero, // inherit parent
lpCurrentDirectory: null, // inherit parent
lpStartupInfo: ref si,
lpProcessInformation: out pi)) {
var error = Marshal.GetLastWin32Error();
if(error == ERROR_ELEVATION_REQUIRED) {
// Unfortunately, ShellExecute() does not allow us to run program without
// CREATE_NEW_CONSOLE, so we can not replace CreateProcess() completely.
// The good news is we are okay with CREATE_NEW_CONSOLE when we run program with elevation.
Process process = new Process();
process.StartInfo = new ProcessStartInfo(path, cmd_args);
process.StartInfo.UseShellExecute = true;
try {
process.Start();
}
catch(Win32Exception exception) {
return exception.ErrorCode;
}
process.WaitForExit();
return process.ExitCode;
}
return error;
}
WaitForSingleObject(pi.hProcess, INFINITE);
uint exit_code = 0;
GetExitCodeProcess(pi.hProcess, out exit_code);
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return (int)exit_code;
}
// now uses GetArgs instead
static string Serialize(string[] args) {
return string.Join(" ", args.Select(a => a.Contains(' ') ? '"' + a + '"' : a));
}
// strips the program name from the command line, returns just the arguments
static string GetArgs(string cmdLine) {
if(cmdLine.StartsWith("\"")) {
var endQuote = cmdLine.IndexOf("\" ", 1);
if(endQuote < 0) return "";
return cmdLine.Substring(endQuote + 1);
}
var space = cmdLine.IndexOf(' ');
if(space < 0 || space == cmdLine.Length - 1) return "";
return cmdLine.Substring(space + 1);
}
static string Get(Dictionary<string, string> dic, string key) {
string value = null;
dic.TryGetValue(key, out value);
return value;
}
static Dictionary<string, string> Config(string path) {
var config = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach(var line in File.ReadAllLines(path)) {
var m = Regex.Match(line, @"([^=]+)=(.*)");
if(m.Success) {
config[m.Groups[1].Value.Trim()] = m.Groups[2].Value.Trim();
}
}
return config;
}
}
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props" Condition="Exists('packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{381F9D2E-2355-4F84-9206-06BB9175F97B}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Scoop.Shim</RootNamespace>
<AssemblyName>Scoop.Shim</AssemblyName>
<TargetFrameworkVersion>v4.5.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="shim.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props'))" />
</Target>
</Project>

View File

@@ -0,0 +1,10 @@
# https://github.com/edymtt/nugetstandalone
$destinationFolder = "$PSScriptRoot\packages"
if (!(Test-Path -Path $destinationFolder)) {
Write-Host -f Red "Run .\install.ps1 first!"
exit 1
}
nuget update packages.config -r $destinationFolder
Remove-Item $destinationFolder -Force -Recurse | Out-Null
nuget install packages.config -o $destinationFolder -ExcludeVersion

2
supporting/shims/kiennq/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.zip
*.bak

View File

@@ -0,0 +1,52 @@
VER?=2.2.1
ZIP=shimexe.zip
URL?=https://github.com/kiennq/scoop-better-shimexe/releases/download/$(VER)/$(ZIP)
LATEST_URL?=https://github.com/kiennq/scoop-better-shimexe/releases/latest
NEWVER=$(shell cat version.txt)
all: verify ## make download unzip verify
version.txt:
@curl --max-redirs 0 -s -D - -o /dev/null $(LATEST_URL) | grep -i ^location | sed -E -e "s|.*/([^/]+)$$|\1|" >version.txt
@printf "%s " "Latest version is:"
@cat version.txt
check: version.txt ## Check the version number in version.txt and update if needed
bump: check ## Bump version number in Makefile
@rm -f Makefile.bak
@sed -i.bak -e 's|=$(VER)|=$(NEWVER)|' Makefile
@cmp --quiet Makefile{,.bak} || echo "Makefile bumped from $(VER) to $(NEWVER)"
$(ZIP): version.txt
curl -L -s -o $(ZIP) $(URL)
@touch $@
download: $(ZIP) ## Download shim from https://github.com/kiennq/scoop-better-shimexe
shim.exe: $(ZIP)
unzip -z -j -o $(ZIP)
@touch $@
unzip: shim.exe ## Unzip download
verify: shim.exe ## Verify SHA256 checksum for shim.exe
sed -e "s|bin/||" checksum.sha256 | sha256sum -c
clean: ## Clean .zip files
rm -f *.zip
help: ## Display help text
@printf "%-8s %s\n" Target Description
@printf "%-8s %s\n" '--------' '------------------------------------------'
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-8s %s\n", $$1, $$2}'
.PHONY: all
.PHONY: bump
.PHONY: check
.PHONY: clean
.PHONY: download
.PHONY: help
.PHONY: unzip
.PHONY: verify

View File

@@ -1 +1 @@
140e3801d8adeda639a21b14e62b93a4c7d26b7a758421f43c82be59753be49b *shim.exe aa685053f4a5c0e7145f2a27514c8a56ceae25b0824062326f04037937caa558 bin/shim.exe

View File

@@ -1 +1 @@
59d9da9f9714003b915bcafbe1b41f53b121dde206ecc23984f62273e957766eece8d64ffc53011c328d3a2ad627aa0f4f7c39bbec8e7b64d0d2ee7b7e771423 *shim.exe 67c605c8163869d8ef8153c64eb09b82645cbae8228928c0fef944d0259a7b2d3791ecf4b4b01e23566916a878ee7977bfc1a59846bccf3c63bd6a1cf4f521b5 bin/shim.exe

Binary file not shown.

View File

@@ -1 +1 @@
v3.1.2 2.2.1

View File

@@ -1 +0,0 @@
0116068768fc992fc536738396b33db3dafe6b0cf0e6f54f6d1aa8b0331f3cec *shim.exe

View File

@@ -1 +0,0 @@
d734c528e9f20581ed3c7aa71a458f7dff7e2780fa0c319ccb9c813cd8dbf656bd7e550b81d2aa3ee8775bff9a4e507bc0b25f075697405adca0f47d37835848 *shim.exe

Binary file not shown.

View File

@@ -1 +0,0 @@
1.1.0

View File

@@ -1,4 +1,4 @@
e1e27af7b07eeedf5ce71a9255f0422816a6fc5849a483c6714e1b472044fa9d *Newtonsoft.Json.dll b624949df8b0e3a6153fdfb730a7c6f4990b6592ee0d922e1788433d276610f3 *Newtonsoft.Json.dll
7496d5349a123a6e3696085662b2ff17b156ccdb0e30e0c396ac72d2da36ce1c *Newtonsoft.Json.Schema.dll 9abb57d73d82a2d77008321a85aff2b62e5ac68bebb54ece8668c96cc112e36b *Newtonsoft.Json.Schema.dll
83b1006443e8c340ca4c631614fc2ce0d5cb9a28c851e3b59724299f58b1397f *Scoop.Validator.dll 0318c8221ce4d44806f8def619bcc02886be0902aab80080e6251c50c6ca53a9 *Scoop.Validator.dll
87f8f8db2202a3fbef6f431d0b7e20cec9d32095c441927402041f3c4076c1b6 *validator.exe 40a70bee96d108701f8f2e81392f9b79fd003f1cb4e1653ad2429753153fd7ee *validator.exe

View File

@@ -1,4 +1,4 @@
56eb7f070929b239642dab729537dde2c2287bdb852ad9e80b5358c74b14bc2b2dded910d0e3b6304ea27eb587e5f19db0a92e1cbae6a70fb20b4ef05057e4ac *Newtonsoft.Json.dll 2fdf035661f349206f58ea1feed8805b7f9517a21f9c113e7301c69de160f184c774350a12a710046e3ff6baa37345d319b6f47fd24fbba4e042d54014bee511 *Newtonsoft.Json.dll
78b12beb1e67ac4f6efa0fcba57b4b34ea6a31d8b369934d6b6a6617386ef9939ea453ac262916e5857ce0359eb809424ea33c676a87a8fdfd77a59b2ce96db0 *Newtonsoft.Json.Schema.dll 855ab2e30c9d523c9f321ae861c5969244185f660fa47e05cec96df8e2970d19843dbd3d89a0fca845544641915d1adf4b4a2145ef568dd99da7791e5064d70e *Newtonsoft.Json.Schema.dll
e9da4370aee4df47eedcf15d9749712eee513e5a9115b808617ddfcfde5bc47a0410edfb57508fcf51033c0be967611b2fd2c2ba944de7290c020cc67f77ac57 *Scoop.Validator.dll 338793e6127330c0b05728291fcf18441127ffb56e1bd5c0f0588cd7436605f4b852f4bb622f655896a7eb7b1262add142b200fd5f37391b47d1401becb6b81c *Scoop.Validator.dll
58a0c37e98cac17822c7756bf6686a5fb74e711b8d986d13bd2f689f6b3b1f485fcd908d92cbc6a162a0e5974c2c5a43de57d15f1996be0aa405e41ec2ec8393 *validator.exe d497c27b48f44f4cff270d3c8801b0cecc74108f8786a4a7c40e57541308ae33a69f5456cfc43ae1ce4214038d20da9fbeac1bcf76cc58d972863b58dab18401 *validator.exe

View File

@@ -4,7 +4,7 @@ Push-Location $PSScriptRoot
. "$PSScriptRoot\..\..\lib\install.ps1" . "$PSScriptRoot\..\..\lib\install.ps1"
if (!$Fast) { if (!$Fast) {
Write-Host 'Install dependencies ...' Write-Host "Install dependencies ..."
& "$PSScriptRoot\install.ps1" & "$PSScriptRoot\install.ps1"
} }

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net45" /> <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net45" />
<package id="Newtonsoft.Json.Schema" version="4.0.1" targetFramework="net45" /> <package id="Newtonsoft.Json.Schema" version="3.0.15-beta2" targetFramework="net45" />
<package id="Microsoft.Net.Compilers.Toolset" version="4.10.0" targetFramework="net45" <package id="Microsoft.Net.Compilers.Toolset" version="4.2.0" targetFramework="net45" developmentDependency="true" />
developmentDependency="true" />
</packages> </packages>

View File

@@ -1,10 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import <Import Project="packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props" Condition="Exists('packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props')" />
Project="packages\Microsoft.Net.Compilers.Toolset.4.10.0\build\Microsoft.Net.Compilers.Toolset.props" <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
Condition="Exists('packages\Microsoft.Net.Compilers.Toolset.4.10.0\build\Microsoft.Net.Compilers.Toolset.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@@ -17,14 +14,12 @@
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed"> <HintPath>packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<HintPath>packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference <Reference Include="Newtonsoft.Json.Schema, Version=3.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
Include="Newtonsoft.Json.Schema, Version=4.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed"> <HintPath>packages\Newtonsoft.Json.Schema.3.0.15-beta2\lib\net45\Newtonsoft.Json.Schema.dll</HintPath>
<HintPath>packages\Newtonsoft.Json.Schema.4.0.1\lib\net45\Newtonsoft.Json.Schema.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
@@ -46,12 +41,8 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
Enable NuGet Package Restore to download them. For more information, see
http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
</PropertyGroup> </PropertyGroup>
<Error <Error Condition="!Exists('packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Net.Compilers.Toolset.4.2.0\build\Microsoft.Net.Compilers.Toolset.props'))" />
Condition="!Exists('packages\Microsoft.Net.Compilers.Toolset.4.10.0\build\Microsoft.Net.Compilers.Toolset.props')"
Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Net.Compilers.Toolset.4.10.0\build\Microsoft.Net.Compilers.Toolset.props'))" />
</Target> </Target>
</Project> </Project>

View File

@@ -7,7 +7,8 @@ BeforeDiscovery {
'[\\/]\.git[\\/]', '[\\/]\.git[\\/]',
'\.sublime-workspace$', '\.sublime-workspace$',
'\.DS_Store$', '\.DS_Store$',
'supporting(\\|/)validator(\\|/)packages(\\|/)*' 'supporting(\\|/)validator(\\|/)packages(\\|/)*',
'supporting(\\|/)shimexe(\\|/)packages(\\|/)*'
) )
$repo_files = (Get-ChildItem $TestPath -File -Recurse).FullName | $repo_files = (Get-ChildItem $TestPath -File -Recurse).FullName |
Where-Object { $_ -inotmatch $($project_file_exclusions -join '|') } Where-Object { $_ -inotmatch $($project_file_exclusions -join '|') }

View File

@@ -0,0 +1,45 @@
BeforeAll {
. "$PSScriptRoot\Scoop-TestLib.ps1"
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\help.ps1"
. "$PSScriptRoot\..\libexec\scoop-alias.ps1" | Out-Null
}
Describe 'Manipulate Alias' -Tag 'Scoop' {
BeforeAll {
Mock shimdir { "$TestDrive\shims" }
Mock set_config { }
Mock get_config { @{} }
$shimdir = shimdir
ensure $shimdir
}
It 'Creates a new alias if alias doesn''t exist' {
$alias_file = "$shimdir\scoop-rm.ps1"
$alias_file | Should -Not -Exist
add_alias 'rm' '"hello, world!"'
& $alias_file | Should -Be 'hello, world!'
}
It 'Does not change existing alias if alias exists' {
$alias_file = "$shimdir\scoop-rm.ps1"
New-Item $alias_file -Type File -Force
$alias_file | Should -Exist
add_alias 'rm' 'test'
& $alias_file | Should -Not -Be 'test'
}
It 'Removes an existing alias' {
$alias_file = "$shimdir\scoop-rm.ps1"
add_alias 'rm' '"hello, world!"'
$alias_file | Should -Exist
Mock get_config { @(@{'rm' = 'scoop-rm' }) }
rm_alias 'rm'
$alias_file | Should -Not -Exist
}
}

View File

@@ -1,45 +0,0 @@
BeforeAll {
. "$PSScriptRoot\Scoop-TestLib.ps1"
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\commands.ps1"
}
Describe 'Manipulate Alias' -Tag 'Scoop' {
BeforeAll {
Mock shimdir { "$TestDrive\shims" }
Mock set_config {}
Mock get_config { @{} }
$shimdir = shimdir
ensure $shimdir
}
It 'Creates a new alias if it does not exist' {
$alias_script = "$shimdir\scoop-rm.ps1"
$alias_script | Should -Not -Exist
add_alias 'rm' '"hello, world!"'
& $alias_script | Should -Be 'hello, world!'
}
It 'Skips an existing alias' {
$alias_script = "$shimdir\scoop-rm.ps1"
Mock abort {}
New-Item $alias_script -Type File -Force
$alias_script | Should -Exist
add_alias 'rm' '"test"'
Should -Invoke -CommandName abort -Times 1 -ParameterFilter { $msg -eq "File 'scoop-rm.ps1' already exists in shims directory." }
}
It 'Removes an existing alias' {
$alias_script = "$shimdir\scoop-rm.ps1"
$alias_script | Should -Exist
Mock get_config { @(@{'rm' = 'scoop-rm' }) }
Mock info {}
rm_alias 'rm'
$alias_script | Should -Not -Exist
Should -Invoke -CommandName info -Times 1 -ParameterFilter { $msg -eq "Removing alias 'rm'..." }
}
}

View File

@@ -1,7 +1,6 @@
BeforeAll { BeforeAll {
. "$PSScriptRoot\Scoop-TestLib.ps1" . "$PSScriptRoot\Scoop-TestLib.ps1"
. "$PSScriptRoot\..\lib\core.ps1" . "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\system.ps1"
. "$PSScriptRoot\..\lib\install.ps1" . "$PSScriptRoot\..\lib\install.ps1"
} }
@@ -73,6 +72,28 @@ Describe 'Test-HelperInstalled' -Tag 'Scoop' {
} }
} }
Describe 'Test-Aria2Enabled' -Tag 'Scoop' {
It 'should return true if aria2 is installed' {
Mock Test-HelperInstalled { $true }
Mock get_config { $true }
Test-Aria2Enabled | Should -BeTrue
}
It 'should return false if aria2 is not installed' {
Mock Test-HelperInstalled { $false }
Mock get_config { $false }
Test-Aria2Enabled | Should -BeFalse
Mock Test-HelperInstalled { $false }
Mock get_config { $true }
Test-Aria2Enabled | Should -BeFalse
Mock Test-HelperInstalled { $true }
Mock get_config { $false }
Test-Aria2Enabled | Should -BeFalse
}
}
Describe 'Test-CommandAvailable' -Tag 'Scoop' { Describe 'Test-CommandAvailable' -Tag 'Scoop' {
It 'should return true if command exists' { It 'should return true if command exists' {
Test-CommandAvailable 'Write-Host' | Should -BeTrue Test-CommandAvailable 'Write-Host' | Should -BeTrue
@@ -146,7 +167,7 @@ Describe 'shim' -Tag 'Scoop', 'Windows' {
BeforeAll { BeforeAll {
$working_dir = setup_working 'shim' $working_dir = setup_working 'shim'
$shimdir = shimdir $shimdir = shimdir
Add-Path $shimdir $(ensure_in_path $shimdir) | Out-Null
} }
It "links a file onto the user's path" { It "links a file onto the user's path" {
@@ -180,7 +201,7 @@ Describe 'rm_shim' -Tag 'Scoop', 'Windows' {
BeforeAll { BeforeAll {
$working_dir = setup_working 'shim' $working_dir = setup_working 'shim'
$shimdir = shimdir $shimdir = shimdir
Add-Path $shimdir $(ensure_in_path $shimdir) | Out-Null
} }
It 'removes shim from path' { It 'removes shim from path' {
@@ -199,7 +220,7 @@ Describe 'get_app_name_from_shim' -Tag 'Scoop', 'Windows' {
BeforeAll { BeforeAll {
$working_dir = setup_working 'shim' $working_dir = setup_working 'shim'
$shimdir = shimdir $shimdir = shimdir
Add-Path $shimdir $(ensure_in_path $shimdir) | Out-Null
Mock appsdir { $working_dir } Mock appsdir { $working_dir }
} }
@@ -237,20 +258,33 @@ Describe 'get_app_name_from_shim' -Tag 'Scoop', 'Windows' {
} }
} }
Describe 'cache_path' -Tag 'Scoop' { Describe 'ensure_robocopy_in_path' -Tag 'Scoop', 'Windows' {
It 'returns the correct cache path for a given input' { BeforeAll {
$url = 'https://example.com/git.zip' $shimdir = shimdir $false
$ret = cache_path 'git' '2.44.0' $url Mock versiondir { "$PSScriptRoot\.." }
$inputStream = [System.IO.MemoryStream]::new([System.Text.Encoding]::UTF8.GetBytes($url))
$sha = (Get-FileHash -Algorithm SHA256 -InputStream $inputStream).Hash.ToLower().Substring(0, 7)
$ret | Should -Be "$cachedir\git#2.44.0#$sha.zip"
} }
# # NOTE: Remove this 6 months after the feature ships. It 'shims robocopy when not on path' {
It 'returns the old format cache path for a given input' { Mock Test-CommandAvailable { $false }
Mock Test-Path { $true } Test-CommandAvailable robocopy | Should -Be $false
$ret = cache_path 'git' '2.44.0' 'https://example.com/git.zip'
$ret | Should -Be "$cachedir\git#2.44.0#https_example.com_git.zip" ensure_robocopy_in_path
# "$shimdir/robocopy.ps1" | should -exist
"$shimdir/robocopy.exe" | Should -Exist
# clean up
rm_shim robocopy $(shimdir $false) | Out-Null
}
It 'does not shim robocopy when it is in path' {
Mock Test-CommandAvailable { $true }
Test-CommandAvailable robocopy | Should -Be $true
ensure_robocopy_in_path
# "$shimdir/robocopy.ps1" | should -not -exist
"$shimdir/robocopy.exe" | Should -Not -Exist
} }
} }

View File

@@ -14,7 +14,7 @@ Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' {
function test_extract($extract_fn, $from, $removal) { function test_extract($extract_fn, $from, $removal) {
$to = (strip_ext $from) -replace '\.tar$', '' $to = (strip_ext $from) -replace '\.tar$', ''
& $extract_fn ($from -replace '/', '\') ($to -replace '/', '\') -Removal:$removal -ExtractDir $args[0] & $extract_fn ($from -replace '/', '\') ($to -replace '/', '\') -Removal:$removal
return $to return $to
} }
@@ -25,7 +25,7 @@ Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' {
} }
It 'Test cases should exist and hash should match' { It 'Test cases should exist and hash should match' {
$testcases | Should -Exist $testcases | Should -Exist
(Get-FileHash -Path $testcases -Algorithm SHA256).Hash.ToLower() | Should -Be '591072faabd419b77932b7023e5899b4e05c0bf8e6859ad367398e6bfe1eb203' (Get-FileHash -Path $testcases -Algorithm SHA256).Hash.ToLower() | Should -Be '791bfce192917a2ff225dcdd87d23ae5f720b20178d85e68e4b1b56139cf8e6a'
} }
It 'Test cases should be extracted correctly' { It 'Test cases should be extracted correctly' {
{ Microsoft.PowerShell.Archive\Expand-Archive -Path $testcases -DestinationPath $working_dir } | Should -Not -Throw { Microsoft.PowerShell.Archive\Expand-Archive -Path $testcases -DestinationPath $working_dir } | Should -Not -Throw
@@ -50,42 +50,15 @@ Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' {
$test6_1 = "$working_dir\7ZipTest6.part01.rar" $test6_1 = "$working_dir\7ZipTest6.part01.rar"
$test6_2 = "$working_dir\7ZipTest6.part02.rar" $test6_2 = "$working_dir\7ZipTest6.part02.rar"
$test6_3 = "$working_dir\7ZipTest6.part03.rar" $test6_3 = "$working_dir\7ZipTest6.part03.rar"
$test7 = "$working_dir\NSISTest.exe"
}
AfterEach {
Remove-Item -Path $to -Recurse -Force
} }
It 'extract normal compressed file' { It 'extract normal compressed file' {
$to = test_extract 'Expand-7zipArchive' $test1 $to = test_extract 'Expand-7zipArchive' $test1
$to | Should -Exist $to | Should -Exist
"$to\empty" | Should -Exist "$to\empty" | Should -Exist
(Get-ChildItem $to).Count | Should -Be 4
}
It 'extract "extract_dir" correctly' {
$to = test_extract 'Expand-7zipArchive' $test1 $false 'tmp'
$to | Should -Exist
"$to\empty" | Should -Exist
(Get-ChildItem $to).Count | Should -Be 1 (Get-ChildItem $to).Count | Should -Be 1
} }
It 'extract "extract_dir" with spaces correctly' {
$to = test_extract 'Expand-7zipArchive' $test1 $false 'tmp 2'
$to | Should -Exist
"$to\empty" | Should -Exist
(Get-ChildItem $to).Count | Should -Be 1
}
It 'extract "extract_dir" with nested folder with same name' {
$to = test_extract 'Expand-7zipArchive' $test1 $false 'keep\sub'
$to | Should -Exist
"$to\keep\empty" | Should -Exist
(Get-ChildItem $to).Count | Should -Be 1
(Get-ChildItem "$to\keep").Count | Should -Be 1
}
It 'extract nested compressed file' { It 'extract nested compressed file' {
# file ext: tgz # file ext: tgz
$to = test_extract 'Expand-7zipArchive' $test2 $to = test_extract 'Expand-7zipArchive' $test2
@@ -121,45 +94,62 @@ Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' {
(Get-ChildItem $to).Count | Should -Be 1 (Get-ChildItem $to).Count | Should -Be 1
} }
It 'extract NSIS installer' {
$to = test_extract 'Expand-7zipArchive' $test7
$to | Should -Exist
"$to\empty" | Should -Exist
(Get-ChildItem $to).Count | Should -Be 1
}
It 'self-extract NSIS installer' {
$to = "$working_dir\NSIS Test"
$null = Invoke-ExternalCommand -FilePath $test7 -ArgumentList @('/S', '/NCRC', "/D=$to")
$to | Should -Exist
"$to\empty" | Should -Exist
(Get-ChildItem $to).Count | Should -Be 1
}
It 'works with "-Removal" switch ($removal param)' { It 'works with "-Removal" switch ($removal param)' {
$test1 | Should -Exist $test1 | Should -Exist
$to = test_extract 'Expand-7zipArchive' $test1 $true test_extract 'Expand-7zipArchive' $test1 $true
$to | Should -Exist
$test1 | Should -Not -Exist $test1 | Should -Not -Exist
$test5_1 | Should -Exist $test5_1 | Should -Exist
$test5_2 | Should -Exist $test5_2 | Should -Exist
$test5_3 | Should -Exist $test5_3 | Should -Exist
$to = test_extract 'Expand-7zipArchive' $test5_1 $true test_extract 'Expand-7zipArchive' $test5_1 $true
$to | Should -Exist
$test5_1 | Should -Not -Exist $test5_1 | Should -Not -Exist
$test5_2 | Should -Not -Exist $test5_2 | Should -Not -Exist
$test5_3 | Should -Not -Exist $test5_3 | Should -Not -Exist
$test6_1 | Should -Exist $test6_1 | Should -Exist
$test6_2 | Should -Exist $test6_2 | Should -Exist
$test6_3 | Should -Exist $test6_3 | Should -Exist
$to = test_extract 'Expand-7zipArchive' $test6_1 $true test_extract 'Expand-7zipArchive' $test6_1 $true
$to | Should -Exist
$test6_1 | Should -Not -Exist $test6_1 | Should -Not -Exist
$test6_2 | Should -Not -Exist $test6_2 | Should -Not -Exist
$test6_3 | Should -Not -Exist $test6_3 | Should -Not -Exist
} }
} }
Context 'zstd extraction' {
BeforeAll {
if ($env:CI) {
Mock Get-AppFilePath { $env:SCOOP_ZSTD_PATH } -ParameterFilter { $Helper -eq 'zstd' }
Mock Get-AppFilePath { '7z.exe' } -ParameterFilter { $Helper -eq '7zip' }
} elseif (!(installed zstd)) {
scoop install zstd
}
$test1 = "$working_dir\ZstdTest.zst"
$test2 = "$working_dir\ZstdTest.tar.zst"
}
It 'extract normal compressed file' {
$to = test_extract 'Expand-ZstdArchive' $test1
$to | Should -Exist
"$to\ZstdTest" | Should -Exist
(Get-ChildItem $to).Count | Should -Be 1
}
It 'extract nested compressed file' {
$to = test_extract 'Expand-ZstdArchive' $test2
$to | Should -Exist
"$to\ZstdTest" | Should -Exist
(Get-ChildItem $to).Count | Should -Be 1
}
It 'works with "-Removal" switch ($removal param)' {
$test1 | Should -Exist
test_extract 'Expand-ZstdArchive' $test1 $true
$test1 | Should -Not -Exist
}
}
Context 'msi extraction' { Context 'msi extraction' {
BeforeAll { BeforeAll {
@@ -168,13 +158,11 @@ Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' {
} elseif (!(installed lessmsi)) { } elseif (!(installed lessmsi)) {
scoop install lessmsi scoop install lessmsi
} }
Copy-Item "$working_dir\MSITest.msi" "$working_dir\MSI Test.msi"
$test1 = "$working_dir\MSITest.msi" $test1 = "$working_dir\MSITest.msi"
$test2 = "$working_dir\MSI Test.msi" $test2 = "$working_dir\MSITestNull.msi"
$test3 = "$working_dir\MSITestNull.msi"
} }
It 'extract normal MSI file using msiexec' { It 'extract normal MSI file' {
Mock get_config { $false } Mock get_config { $false }
$to = test_extract 'Expand-MsiArchive' $test1 $to = test_extract 'Expand-MsiArchive' $test1
$to | Should -Exist $to | Should -Exist
@@ -182,29 +170,9 @@ Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' {
(Get-ChildItem "$to\MSITest").Count | Should -Be 1 (Get-ChildItem "$to\MSITest").Count | Should -Be 1
} }
It 'extract normal MSI file with whitespace in path using msiexec' {
Mock get_config { $false }
$to = test_extract 'Expand-MsiArchive' $test2
$to | Should -Exist
"$to\MSITest\empty" | Should -Exist
(Get-ChildItem "$to\MSITest").Count | Should -Be 1
}
It 'extract normal MSI file using lessmsi' {
Mock get_config { $true }
$to = test_extract 'Expand-MsiArchive' $test1
$to | Should -Exist
}
It 'extract normal MSI file with whitespace in path using lessmsi' {
Mock get_config { $true }
$to = test_extract 'Expand-MsiArchive' $test2
$to | Should -Exist
}
It 'extract empty MSI file using lessmsi' { It 'extract empty MSI file using lessmsi' {
Mock get_config { $true } Mock get_config { $true }
$to = test_extract 'Expand-MsiArchive' $test3 $to = test_extract 'Expand-MsiArchive' $test2
$to | Should -Exist $to | Should -Exist
} }

View File

@@ -14,6 +14,11 @@ Describe 'Package Dependencies' -Tag 'Scoop' {
Test-7zipRequirement -Uri 'test.bin' | Should -BeFalse Test-7zipRequirement -Uri 'test.bin' | Should -BeFalse
Test-7zipRequirement -Uri @('test.xz', 'test.bin') | Should -BeTrue Test-7zipRequirement -Uri @('test.xz', 'test.bin') | Should -BeTrue
} }
It 'Test Zstd requirement' {
Test-ZstdRequirement -Uri 'test.zst' | Should -BeTrue
Test-ZstdRequirement -Uri 'test.bin' | Should -BeFalse
Test-ZstdRequirement -Uri @('test.zst', 'test.bin') | Should -BeTrue
}
It 'Test lessmsi requirement' { It 'Test lessmsi requirement' {
Mock get_config { $true } Mock get_config { $true }
Test-LessmsiRequirement -Uri 'test.msi' | Should -BeTrue Test-LessmsiRequirement -Uri 'test.msi' | Should -BeTrue
@@ -22,6 +27,7 @@ Describe 'Package Dependencies' -Tag 'Scoop' {
} }
It 'Allow $Uri be $null' { It 'Allow $Uri be $null' {
Test-7zipRequirement -Uri $null | Should -BeFalse Test-7zipRequirement -Uri $null | Should -BeFalse
Test-ZstdRequirement -Uri $null | Should -BeFalse
Test-LessmsiRequirement -Uri $null | Should -BeFalse Test-LessmsiRequirement -Uri $null | Should -BeFalse
} }
} }
@@ -65,7 +71,6 @@ Describe 'Package Dependencies' -Tag 'Scoop' {
BeforeAll { BeforeAll {
Mock Test-HelperInstalled { $false } Mock Test-HelperInstalled { $false }
Mock get_config { $true } -ParameterFilter { $name -eq 'USE_LESSMSI' } Mock get_config { $true } -ParameterFilter { $name -eq 'USE_LESSMSI' }
Mock get_config { $false } -ParameterFilter { $name -eq 'USE_EXTERNAL_7ZIP' }
Mock Get-Manifest { 'lessmsi', @{}, $null, $null } -ParameterFilter { $app -eq 'lessmsi' } Mock Get-Manifest { 'lessmsi', @{}, $null, $null } -ParameterFilter { $app -eq 'lessmsi' }
Mock Get-Manifest { '7zip', @{ url = 'test.msi' }, $null, $null } -ParameterFilter { $app -eq '7zip' } Mock Get-Manifest { '7zip', @{ url = 'test.msi' }, $null, $null } -ParameterFilter { $app -eq '7zip' }
Mock Get-Manifest { 'innounp', @{}, $null, $null } -ParameterFilter { $app -eq 'innounp' } Mock Get-Manifest { 'innounp', @{}, $null, $null } -ParameterFilter { $app -eq 'innounp' }

View File

@@ -1,49 +0,0 @@
BeforeAll {
. "$PSScriptRoot\Scoop-TestLib.ps1"
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\download.ps1"
}
Describe 'Test-Aria2Enabled' -Tag 'Scoop' {
It 'should return true if aria2 is installed' {
Mock Test-HelperInstalled { $true }
Mock get_config { $true }
Test-Aria2Enabled | Should -BeTrue
}
It 'should return false if aria2 is not installed' {
Mock Test-HelperInstalled { $false }
Mock get_config { $false }
Test-Aria2Enabled | Should -BeFalse
Mock Test-HelperInstalled { $false }
Mock get_config { $true }
Test-Aria2Enabled | Should -BeFalse
Mock Test-HelperInstalled { $true }
Mock get_config { $false }
Test-Aria2Enabled | Should -BeFalse
}
}
Describe 'url_filename' -Tag 'Scoop' {
It 'should extract the real filename from an url' {
url_filename 'http://example.org/foo.txt' | Should -Be 'foo.txt'
url_filename 'http://example.org/foo.txt?var=123' | Should -Be 'foo.txt'
}
It 'can be tricked with a hash to override the real filename' {
url_filename 'http://example.org/foo-v2.zip#/foo.zip' | Should -Be 'foo.zip'
}
}
Describe 'url_remote_filename' -Tag 'Scoop' {
It 'should extract the real filename from an url' {
url_remote_filename 'http://example.org/foo.txt' | Should -Be 'foo.txt'
url_remote_filename 'http://example.org/foo.txt?var=123' | Should -Be 'foo.txt'
}
It 'can not be tricked with a hash to override the real filename' {
url_remote_filename 'http://example.org/foo-v2.zip#/foo.zip' | Should -Be 'foo-v2.zip'
}
}

View File

@@ -17,12 +17,6 @@ Describe 'getopt' -Tag 'Scoop' {
$err | Should -Be 'Option --arb requires an argument.' $err | Should -Be 'Option --arb requires an argument.'
} }
It 'handle space in quote' {
$opt, $rem, $err = getopt '-x', 'space arg' 'x:' ''
$err | Should -BeNullOrEmpty
$opt.x | Should -Be 'space arg'
}
It 'handle unrecognized short option' { It 'handle unrecognized short option' {
$null, $null, $err = getopt '-az' 'a' '' $null, $null, $err = getopt '-az' 'a' ''
$err | Should -Be 'Option -z not recognized.' $err | Should -Be 'Option -z not recognized.'

View File

@@ -1,7 +1,6 @@
BeforeAll { BeforeAll {
. "$PSScriptRoot\Scoop-TestLib.ps1" . "$PSScriptRoot\Scoop-TestLib.ps1"
. "$PSScriptRoot\..\lib\core.ps1" . "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\system.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1" . "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\install.ps1" . "$PSScriptRoot\..\lib\install.ps1"
} }
@@ -12,10 +11,34 @@ Describe 'appname_from_url' -Tag 'Scoop' {
} }
} }
Describe 'url_filename' -Tag 'Scoop' {
It 'should extract the real filename from an url' {
url_filename 'http://example.org/foo.txt' | Should -Be 'foo.txt'
url_filename 'http://example.org/foo.txt?var=123' | Should -Be 'foo.txt'
}
It 'can be tricked with a hash to override the real filename' {
url_filename 'http://example.org/foo-v2.zip#/foo.zip' | Should -Be 'foo.zip'
}
}
Describe 'url_remote_filename' -Tag 'Scoop' {
It 'should extract the real filename from an url' {
url_remote_filename 'http://example.org/foo.txt' | Should -Be 'foo.txt'
url_remote_filename 'http://example.org/foo.txt?var=123' | Should -Be 'foo.txt'
}
It 'can not be tricked with a hash to override the real filename' {
url_remote_filename 'http://example.org/foo-v2.zip#/foo.zip' | Should -Be 'foo-v2.zip'
}
}
Describe 'is_in_dir' -Tag 'Scoop', 'Windows' { Describe 'is_in_dir' -Tag 'Scoop', 'Windows' {
It 'should work correctly' { It 'should work correctly' {
is_in_dir 'C:\test' 'C:\foo' | Should -BeFalse is_in_dir 'C:\test' 'C:\foo' | Should -BeFalse
is_in_dir 'C:\test' 'C:\test\foo\baz.zip' | Should -BeTrue is_in_dir 'C:\test' 'C:\test\foo\baz.zip' | Should -BeTrue
is_in_dir 'test' "$PSScriptRoot" | Should -BeTrue
is_in_dir "$PSScriptRoot\..\" "$PSScriptRoot" | Should -BeFalse is_in_dir "$PSScriptRoot\..\" "$PSScriptRoot" | Should -BeFalse
} }
} }
@@ -24,28 +47,27 @@ Describe 'env add and remove path' -Tag 'Scoop', 'Windows' {
BeforeAll { BeforeAll {
# test data # test data
$manifest = @{ $manifest = @{
'env_add_path' = @('foo', 'bar', '.', '..') 'env_add_path' = @('foo', 'bar')
} }
$testdir = Join-Path $PSScriptRoot 'path-test-directory' $testdir = Join-Path $PSScriptRoot 'path-test-directory'
$global = $false $global = $false
# store the original path to prevent leakage of tests
$origPath = $env:PATH
} }
It 'should concat the correct path' { It 'should concat the correct path' {
Mock Add-Path {} Mock add_first_in_path {}
Mock Remove-Path {} Mock remove_from_path {}
# adding # adding
env_add_path $manifest $testdir $global env_add_path $manifest $testdir $global
Should -Invoke -CommandName Add-Path -Times 1 -ParameterFilter { $Path -like "$testdir\foo" } Assert-MockCalled add_first_in_path -Times 1 -ParameterFilter { $dir -like "$testdir\foo" }
Should -Invoke -CommandName Add-Path -Times 1 -ParameterFilter { $Path -like "$testdir\bar" } Assert-MockCalled add_first_in_path -Times 1 -ParameterFilter { $dir -like "$testdir\bar" }
Should -Invoke -CommandName Add-Path -Times 1 -ParameterFilter { $Path -like $testdir }
Should -Invoke -CommandName Add-Path -Times 0 -ParameterFilter { $Path -like $PSScriptRoot }
env_rm_path $manifest $testdir $global env_rm_path $manifest $testdir $global
Should -Invoke -CommandName Remove-Path -Times 1 -ParameterFilter { $Path -like "$testdir\foo" } Assert-MockCalled remove_from_path -Times 1 -ParameterFilter { $dir -like "$testdir\foo" }
Should -Invoke -CommandName Remove-Path -Times 1 -ParameterFilter { $Path -like "$testdir\bar" } Assert-MockCalled remove_from_path -Times 1 -ParameterFilter { $dir -like "$testdir\bar" }
Should -Invoke -CommandName Remove-Path -Times 1 -ParameterFilter { $Path -like $testdir }
Should -Invoke -CommandName Remove-Path -Times 0 -ParameterFilter { $Path -like $PSScriptRoot }
} }
} }

View File

@@ -1,13 +1,13 @@
# copies fixtures to a working directory # copies fixtures to a working directory
function setup_working($name) { function setup_working($name) {
$fixtures = "$PSScriptRoot\fixtures\$name" $fixtures = "$PSScriptRoot/fixtures/$name"
if (!(Test-Path $fixtures)) { if (!(Test-Path $fixtures)) {
Write-Host "couldn't find fixtures for $name at $fixtures" -f red Write-Host "couldn't find fixtures for $name at $fixtures" -f red
exit 1 exit 1
} }
# reset working dir # reset working dir
$working_dir = "$([IO.Path]::GetTempPath())ScoopTestFixtures\$name" $working_dir = "$([IO.Path]::GetTempPath())ScoopTestFixtures/$name"
if (Test-Path $working_dir) { if (Test-Path $working_dir) {
Remove-Item -Recurse -Force $working_dir Remove-Item -Recurse -Force $working_dir

View File

@@ -99,7 +99,7 @@ Describe 'versions comparison' -Tag 'Scoop' {
Compare-Version 'nightly-20190801' 'nightly-20200801' | Should -Be 0 Compare-Version 'nightly-20190801' 'nightly-20200801' | Should -Be 0
} }
It "handles nightly versions with 'update_nightly'" { It 'handles nightly versions with `update_nightly`' {
function get_config { $true } function get_config { $true }
Mock Get-Date { '20200801' } Mock Get-Date { '20200801' }
Compare-Version 'nightly-20200801' 'nightly' | Should -Be 0 Compare-Version 'nightly-20200801' 'nightly' | Should -Be 0

View File

@@ -70,6 +70,19 @@ if ($env:CI -eq $true) {
Invoke-WebRequest -Uri $source -OutFile $destination Invoke-WebRequest -Uri $source -OutFile $destination
& 7z.exe x "$env:SCOOP_HELPERS_PATH\innounp.rar" -o"$env:SCOOP_HELPERS_PATH\innounp" -y | Out-Null & 7z.exe x "$env:SCOOP_HELPERS_PATH\innounp.rar" -o"$env:SCOOP_HELPERS_PATH\innounp" -y | Out-Null
} }
# Only download zstd for AppVeyor, GitHub Actions has zstd installed by default
if ($env:BHBuildSystem -eq 'AppVeyor') {
$env:SCOOP_ZSTD_PATH = "$env:SCOOP_HELPERS_PATH\zstd\zstd.exe"
if (!(Test-Path $env:SCOOP_ZSTD_PATH)) {
$source = 'https://github.com/facebook/zstd/releases/download/v1.5.1/zstd-v1.5.1-win32.zip'
$destination = "$env:SCOOP_HELPERS_PATH\zstd.zip"
Invoke-WebRequest -Uri $source -OutFile $destination
& 7z.exe x "$env:SCOOP_HELPERS_PATH\zstd.zip" -o"$env:SCOOP_HELPERS_PATH\zstd" -y | Out-Null
}
} else {
$env:SCOOP_ZSTD_PATH = (Get-Command zstd.exe).Path
}
} }
} }

Binary file not shown.