Skip to content
This repository was archived by the owner on Oct 30, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# Scoop (un)installer
# Shovel installer

Generic installer for any Shovel fork

## Installation

### Prerequisites

- Windows 7 SP1+ / Windows Server 2008+, Windows 10 recommended
- Windows 11/10 recommended
- Windows 7 SP1+ / Windows Server 2008+
- [PowerShell 5](https://aka.ms/wmf5download) or later, [PowerShell Core](https://github.com/PowerShell/PowerShell) included
- [.NET Framework 4.5](https://microsoft.com/net/download) or later
- PowerShell execution policy in `Unrestricted`/`RemoteSigned`/`ByPass`, for example:
Expand All @@ -16,15 +19,15 @@ Run this command from a **non-admin** PowerShell to install scoop with default c
scoop will be install to `C:\Users\<YOUR USERNAME>\scoop`.

```powershell
iwr -useb 'https://raw.githubusercontent.com/scoopinstaller/install/master/install.ps1' | iex
iwr -useb 'https://raw.githubusercontent.com/shovel-org/Install/main/install.ps1' | iex
```

### Advanced Installation

If you want to have an advanced installation. You can download the installer and manually execute it with parameters.

```powershell
iwr -useb 'https://raw.githubusercontent.com/scoopinstaller/install/master/install.ps1' -outfile 'install.ps1'
iwr -useb 'https://raw.githubusercontent.com/shovel-org/Install/main/install.ps1' -outfile 'install.ps1'
```

To see all configurable parameters of the installer.
Expand All @@ -46,13 +49,13 @@ Or you can use the legacy method to configure custom directory by setting Enviro
$env:SCOOP='D:\Applications\Scoop'
$env:SCOOP_GLOBAL='F:\GlobalScoopApps'
[Environment]::SetEnvironmentVariable('SCOOP_GLOBAL', $env:SCOOP_GLOBAL, 'Machine')
iwr -useb 'https://raw.githubusercontent.com/scoopinstaller/install/master/install.ps1' | iex
iwr -useb 'https://raw.githubusercontent.com/shovel-org/Install/main/install.ps1' | iex
```

**For Admin:** Installation under the administrator console has been disabled by default for security reason. If you know what you are doing and want to install Scoop as administrator. Please download the installer and manually execute it with the `-RunAsAdmin` parameter in an elevated console. Here is the example:

```powershell
iwr -useb 'https://raw.githubusercontent.com/scoopinstaller/install/master/install.ps1' -outfile 'install.ps1'
iwr -useb 'https://raw.githubusercontent.com/shovel-org/Install/main/install.ps1' -outfile 'install.ps1'
.\install.ps1 -RunAsAdmin [-OtherParameters ...]
```

Expand Down
105 changes: 55 additions & 50 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,12 @@ param(
# Disable StrictMode in this script
Set-StrictMode -Off

#region Functions
function Write-InstallInfo {
param(
[Parameter(Mandatory = $True, Position = 0)]
[Parameter(Mandatory, Position = 0)]
[String] $String,
[Parameter(Mandatory = $False, Position = 1)]
[Parameter(Position = 1)]
[System.ConsoleColor] $ForegroundColor = $host.UI.RawUI.ForegroundColor
)

Expand All @@ -91,8 +92,8 @@ function Deny-Install {
[Int] $errorCode = 1
)

Write-InstallInfo -String $message -ForegroundColor DarkRed
Write-InstallInfo "Abort."
Write-InstallInfo -String $message -ForegroundColor 'DarkRed'
Write-InstallInfo 'Abort.'

# Don't abort if invoked with iex that would close the PS session
if ($IS_EXECUTED_FROM_IEX) {
Expand All @@ -104,7 +105,7 @@ function Deny-Install {

function Test-ValidateParameter {
if ($null -eq $Proxy -and ($null -ne $ProxyCredential -or $ProxyUseDefaultCredentials)) {
Deny-Install "Provide a valid proxy URI for the -Proxy parameter when using the -ProxyCredential or -ProxyUseDefaultCredentials."
Deny-Install 'Provide a valid proxy URI for the -Proxy parameter when using the -ProxyCredential or -ProxyUseDefaultCredentials.'
}

if ($ProxyUseDefaultCredentials -and $null -ne $ProxyCredential) {
Expand All @@ -113,40 +114,38 @@ function Test-ValidateParameter {
}

function Test-IsAdministrator {
return ([Security.Principal.WindowsPrincipal]`
[Security.Principal.WindowsIdentity]::GetCurrent()`
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Test-Prerequisite {
# Scoop requires PowerShell 5 at least
if (($PSVersionTable.PSVersion.Major) -lt 5) {
Deny-Install "PowerShell 5 or later is required to run Scoop. Go to https://microsoft.com/powershell to get the latest version of PowerShell."
Deny-Install 'PowerShell 5 or later is required to run Scoop. Go to https://microsoft.com/powershell to get the latest version of PowerShell.'
}

# Scoop requires TLS 1.2 SecurityProtocol, which exists in .NET Framework 4.5+
if ([System.Enum]::GetNames([System.Net.SecurityProtocolType]) -notcontains 'Tls12') {
Deny-Install "Scoop requires .NET Framework 4.5+ to work. Go to https://microsoft.com/net/download to get the latest version of .NET Framework."
Deny-Install 'Scoop requires .NET Framework 4.5+ to work. Go to https://microsoft.com/net/download to get the latest version of .NET Framework.'
}

# Ensure Robocopy.exe is accessible
if (!([bool](Get-Command -Name 'robocopy' -ErrorAction SilentlyContinue))) {
Deny-Install "Scoop requires 'C:\Windows\System32\Robocopy.exe' to work. Please make sure 'C:\Windows\System32' is in your PATH."
if (!([bool](Get-Command -Name 'robocopy' -ErrorAction 'SilentlyContinue'))) {
Deny-Install 'Scoop requires ''C:\Windows\System32\Robocopy.exe'' to work. Please make sure ''C:\Windows\System32'' is in your PATH.'
}

# Detect if RunAsAdministrator, there is no need to run as administrator when installing Scoop.
if (!$RunAsAdmin -and (Test-IsAdministrator)) {
Deny-Install "Running the installer as administrator is disabled by default, use -RunAsAdmin parameter if you know what you are doing."
Deny-Install 'Running the installer as administrator is disabled by default, use -RunAsAdmin parameter if you know what you are doing.'
}

# Show notification to change execution policy
$allowedExecutionPolicy = @('Unrestricted', 'RemoteSigned', 'ByPass')
if ((Get-ExecutionPolicy).ToString() -notin $allowedExecutionPolicy) {
Deny-Install "PowerShell requires an execution policy in [$($allowedExecutionPolicy -join ", ")] to run Scoop. For example, to set the execution policy to 'RemoteSigned' please run 'Set-ExecutionPolicy RemoteSigned -Scope CurrentUser'."
Deny-Install "PowerShell requires an execution policy in [$($allowedExecutionPolicy -join ', ')] to run Scoop. For example, to set the execution policy to 'RemoteSigned' please run 'Set-ExecutionPolicy RemoteSigned -Scope CurrentUser'."
}

# Test if scoop is installed, by checking if scoop command exists.
if ([bool](Get-Command -Name 'scoop' -ErrorAction SilentlyContinue)) {
if ([bool](Get-Command -Name 'scoop' -ErrorAction 'SilentlyContinue')) {
Deny-Install "Scoop is already installed. Run 'scoop update' to get the latest version."
}
}
Expand Down Expand Up @@ -177,7 +176,7 @@ function Get-Downloader {
} elseif ($Proxy) {
# Prepend protocol if not provided
if (!$Proxy.IsAbsoluteUri) {
$Proxy = New-Object System.Uri("http://" + $Proxy.OriginalString)
$Proxy = New-Object System.Uri('http://' + $Proxy.OriginalString)
}

$Proxy = New-Object System.Net.WebProxy($Proxy)
Expand Down Expand Up @@ -215,8 +214,7 @@ function Test-isFileLocked {
$stream.Close()
}
return $false
}
catch {
} catch {
# The file is locked by a process.
return $true
}
Expand All @@ -229,14 +227,14 @@ function Expand-ZipArchive {
)

if (!(Test-Path $path)) {
Deny-Install "Unzip failed: can't find $path to unzip."
Deny-Install "Unzip failed: cannot find $path to unzip."
}

# Check if the zip file is locked, by antivirus software for example
$retries = 0
while ($retries -le 10) {
if ($retries -eq 10) {
Deny-Install "Unzip failed: can't unzip because a process is locking the file."
Deny-Install "Unzip failed: cannot unzip because a process is locking the file."
}
if (Test-isFileLocked $path) {
Write-InstallInfo "Waiting for $path to be unlocked by another process... ($retries/10)"
Expand All @@ -256,7 +254,7 @@ function Import-ScoopShim {
$path = "$SCOOP_APP_DIR\bin\scoop.ps1"

if (!(Test-Path $SCOOP_SHIMS_DIR)) {
New-Item -Type Directory $SCOOP_SHIMS_DIR | Out-Null
New-Item -Type 'Directory' $SCOOP_SHIMS_DIR | Out-Null
}

# The scoop shim
Expand All @@ -269,8 +267,8 @@ function Import-ScoopShim {

# Setting PSScriptRoot in Shim if it is not defined, so the shim doesn't break in PowerShell 2.0
Write-Output "if (!(Test-Path Variable:PSScriptRoot)) { `$PSScriptRoot = Split-Path `$MyInvocation.MyCommand.Path -Parent }" | Out-File "$shim.ps1" -Encoding utf8
Write-Output "`$path = Join-Path `"`$PSScriptRoot`" `"$relativePath`"" | Out-File "$shim.ps1" -Encoding utf8 -Append
Write-Output "if (`$MyInvocation.ExpectingInput) { `$input | & `$path @args } else { & `$path @args }" | Out-File "$shim.ps1" -Encoding utf8 -Append
Write-Output "`$path = Join-Path `"`$PSScriptRoot`" `"$relativePath`"" | Out-File "$shim.ps1" -Encoding 'utf8' -Append
Write-Output "if (`$MyInvocation.ExpectingInput) { `$input | & `$path @args } else { & `$path @args }" | Out-File "$shim.ps1" -Encoding 'utf8' -Append

# Make scoop accessible from cmd.exe
Write-Output "@echo off
Expand All @@ -282,10 +280,10 @@ set args=%args:(=``(%
set args=%args:)=``)%
set invalid=`"='
if !args! == !invalid! ( set args= )
powershell -noprofile -ex unrestricted `"& '$path' %args%;exit `$lastexitcode`"" | Out-File "$shim.cmd" -Encoding ascii
powershell -noprofile -ex unrestricted `"& '$path' %args%;exit `$lastexitcode`"" | Out-File "$shim.cmd" -Encoding 'ascii'

# Make scoop accessible from bash or other posix shell
Write-Output "#!/bin/sh`npowershell.exe -ex unrestricted `"$path`" `"$@`"" | Out-File $shim -Encoding ascii
Write-Output "#!/bin/sh`npowershell.exe -ex unrestricted `"$path`" `"$@`"" | Out-File $shim -Encoding 'ascii'
}

function Get-Env {
Expand All @@ -303,13 +301,11 @@ function Add-ShimsDirToPath {
$userEnvPath = Get-Env 'PATH'

if ($userEnvPath -notmatch [Regex]::Escape($SCOOP_SHIMS_DIR)) {
$h = (Get-PsProvider 'FileSystem').Home
if (!$h.EndsWith('\')) {
$h += '\'
}
$h = (Get-PSProvider 'FileSystem').Home
if (!$h.EndsWith('\')) { $h += '\' }

if (!($h -eq '\')) {
$friendlyPath = "$SCOOP_SHIMS_DIR" -Replace ([Regex]::Escape($h)), "~\"
if ($h -ne '\') {
$friendlyPath = "$SCOOP_SHIMS_DIR" -replace ([Regex]::Escape($h)), '~\'
Write-InstallInfo "Adding $friendlyPath to your path."
} else {
Write-InstallInfo "Adding $SCOOP_SHIMS_DIR to your path."
Expand All @@ -328,7 +324,7 @@ function Use-Config {
}

try {
return (Get-Content $SCOOP_CONFIG_FILE -Raw | ConvertFrom-Json -ErrorAction Stop)
return (Get-Content $SCOOP_CONFIG_FILE -Raw | ConvertFrom-Json -ErrorAction 'Stop')
} catch {
Deny-Install "ERROR loading $SCOOP_CONFIG_FILE`: $($_.Exception.Message)"
}
Expand All @@ -343,13 +339,13 @@ function Add-Config {
)

$scoopConfig = Use-Config

if ($scoopConfig -is [System.Management.Automation.PSObject]) {
if ($Value -eq [bool]::TrueString -or $Value -eq [bool]::FalseString) {
$Value = [System.Convert]::ToBoolean($Value)
}
if ($null -eq $scoopConfig.$Name) {
$scoopConfig | Add-Member -MemberType NoteProperty -Name $Name -Value $Value
$scoopConfig | Add-Member -MemberType 'NoteProperty' -Name $Name -Value $Value
} else {
$scoopConfig.$Name = $Value
}
Expand All @@ -360,14 +356,14 @@ function Add-Config {
}

$scoopConfig = New-Object PSObject
$scoopConfig | Add-Member -MemberType NoteProperty -Name $Name -Value $Value
$scoopConfig | Add-Member -MemberType 'NoteProperty' -Name $Name -Value $Value
}

if ($null -eq $Value) {
$scoopConfig.PSObject.Properties.Remove($Name)
}

ConvertTo-Json $scoopConfig | Set-Content $SCOOP_CONFIG_FILE -Encoding ASCII
ConvertTo-Json $scoopConfig | Set-Content $SCOOP_CONFIG_FILE -Encoding 'ASCII'
return $scoopConfig
}

Expand Down Expand Up @@ -408,7 +404,7 @@ function Add-DefaultConfig {
}

function Install-Scoop {
Write-InstallInfo "Initializing..."
Write-InstallInfo 'Initializing...'
# Validate install parameters
Test-ValidateParameter
# Check prerequisites
Expand All @@ -417,23 +413,23 @@ function Install-Scoop {
Optimize-SecurityProtocol

# Download scoop zip from GitHub
Write-InstallInfo "Downloading..."
Write-InstallInfo 'Downloading...'
$downloader = Get-Downloader
# 1. download scoop
$scoopZipfile = "$SCOOP_APP_DIR\scoop.zip"
if (!(Test-Path $SCOOP_APP_DIR)) {
New-Item -Type Directory $SCOOP_APP_DIR | Out-Null
New-Item -Type 'Directory' $SCOOP_APP_DIR | Out-Null
}
$downloader.downloadFile($SCOOP_PACKAGE_REPO, $scoopZipfile)
# 2. download scoop main bucket
$scoopMainZipfile = "$SCOOP_MAIN_BUCKET_DIR\scoop-main.zip"
if (!(Test-Path $SCOOP_MAIN_BUCKET_DIR)) {
New-Item -Type Directory $SCOOP_MAIN_BUCKET_DIR | Out-Null
New-Item -Type 'Directory' $SCOOP_MAIN_BUCKET_DIR | Out-Null
}
$downloader.downloadFile($SCOOP_MAIN_BUCKET_REPO, $scoopMainZipfile)

# Extract files from downloaded zip
Write-InstallInfo "Extracting..."
Write-InstallInfo 'Extracting...'
# 1. extract scoop
$scoopUnzipTempDir = "$SCOOP_APP_DIR\_tmp"
Expand-ZipArchive $scoopZipfile $scoopUnzipTempDir
Expand All @@ -444,22 +440,30 @@ function Install-Scoop {
Copy-Item "$scoopMainUnzipTempDir\Main-*\*" $SCOOP_MAIN_BUCKET_DIR -Recurse -Force

# Cleanup
Remove-Item $scoopUnzipTempDir -Recurse -Force
Remove-Item $scoopZipfile
Remove-Item $scoopMainUnzipTempDir -Recurse -Force
Remove-Item $scoopMainZipfile
Remove-Item $scoopUnzipTempDir, $scoopMainUnzipTempDir -Recurse -Force
Remove-Item $scoopZipfile, $scoopMainZipfile

# Create the scoop shim
Write-InstallInfo "Creating shim..."
Write-InstallInfo 'Creating shim...'
Import-ScoopShim

# Finially ensure scoop shims is in the PATH
Add-ShimsDirToPath
# Setup initial configuration of Scoop
Add-DefaultConfig

Write-InstallInfo "Scoop was installed successfully!" -ForegroundColor DarkGreen
Write-InstallInfo "Type 'scoop help' for instructions."
Write-InstallInfo 'Scoop was installed successfully!' -ForegroundColor 'DarkGreen'
Write-InstallInfo 'Type ''scoop help'' for instructions.'
}
#endregion Functions

#region Main
$NoProxy, $Proxy, $ProxyCredential, $ProxyUseDefaultCredentials, $RunAsAdmin | Out-Null

if (!$env:USERPROFILE) {
if (!$env:HOME) { Deny-Install 'Cannot resolve user''s home directory. USERPROFILE and HOME environment variables are not set.' }

$env:USERPROFILE = $env:HOME
}

# Prepare variables
Expand All @@ -482,8 +486,8 @@ $SCOOP_CONFIG_HOME = $env:XDG_CONFIG_HOME, "$env:USERPROFILE\.config" | Select-O
$SCOOP_CONFIG_FILE = "$SCOOP_CONFIG_HOME\scoop\config.json"

# TODO: Use a specific version of Scoop and the main bucket
$SCOOP_PACKAGE_REPO = "https://github.com/lukesampson/scoop/archive/master.zip"
$SCOOP_MAIN_BUCKET_REPO = "https://github.com/ScoopInstaller/Main/archive/master.zip"
$SCOOP_PACKAGE_REPO = 'https://github.com/ScoopInstaller/Scoop/archive/master.zip'
$SCOOP_MAIN_BUCKET_REPO = 'https://github.com/ScoopInstaller/Main/archive/master.zip'

# Quit if anything goes wrong
$oldErrorActionPreference = $ErrorActionPreference
Expand All @@ -494,3 +498,4 @@ Install-Scoop

# Reset $ErrorActionPreference to original value
$ErrorActionPreference = $oldErrorActionPreference
#endregion Main