diff --git a/install.ps1 b/install.ps1 index c16f16ef..53d3853a 100644 --- a/install.ps1 +++ b/install.ps1 @@ -93,9 +93,9 @@ function Exit-Install { [Int] $ErrorCode = 1 ) - if ($IS_EXECUTED_FROM_IEX) { - # Don't abort with `exit` that would close the PS session if invoked - # with iex, yet set `LASTEXITCODE` for the caller to check + if ((-not $env:CI) -and $IS_EXECUTED_FROM_IEX) { + # Don't abort with `exit` that would close the interactive PS session + # if invoked with iex, yet set `LASTEXITCODE` for the caller to check $Global:LASTEXITCODE = $ErrorCode break } else { @@ -131,6 +131,38 @@ function Test-ValidateParameter { if ($ProxyUseDefaultCredentials -and $null -ne $ProxyCredential) { Deny-Install "ProxyUseDefaultCredentials is conflict with ProxyCredential. Don't use the -ProxyCredential and -ProxyUseDefaultCredentials together." } + + if (-not (Test-Path $SCOOP_DIR -IsValid)) { + Deny-Install "'$SCOOP_DIR' is not a valid path, please specify another path." + } + + if (-not (Test-Path $SCOOP_GLOBAL_DIR -IsValid)) { + Deny-Install "'$SCOOP_GLOBAL_DIR' is not a valid path, please specify another path." + } + + if (-not (Test-Path $SCOOP_CACHE_DIR -IsValid)) { + Deny-Install "'$SCOOP_CACHE_DIR' is not a valid path, please specify another path." + } + + if (Test-Path $SCOOP_DIR -PathType Leaf) { + Deny-Install "'$SCOOP_DIR' is a file, please remove it or specify another path." + } + + if ((Test-Path $SCOOP_DIR -PathType Container) -and (Test-Path "$SCOOP_DIR\*")) { + Deny-Install "'$SCOOP_DIR' exists and is not empty, please specify another path." + } + + if (Test-Path $SCOOP_GLOBAL_DIR -PathType Leaf) { + Deny-Install "'$SCOOP_GLOBAL_DIR' is a file, please remove it or specify another path." + } + + if ((Test-Path $SCOOP_GLOBAL_DIR -PathType Container) -and (Test-Path "$SCOOP_GLOBAL_DIR\*")) { + Deny-Install "'$SCOOP_GLOBAL_DIR' exists and is not empty, please specify another path." + } + + if (Test-Path $SCOOP_CACHE_DIR -PathType Leaf) { + Deny-Install "'$SCOOP_CACHE_DIR' is a file, please remove it or specify another path." + } } function Test-IsAdministrator { @@ -157,8 +189,8 @@ function Test-Prerequisite { # Detect if RunAsAdministrator, there is no need to run as administrator when installing Scoop if (!$RunAsAdmin -and (Test-IsAdministrator)) { - # Exception: Windows Sandbox, GitHub Actions CI - $exception = ($env:USERNAME -eq 'WDAGUtilityAccount') -or ($env:GITHUB_ACTIONS -eq 'true' -and $env:CI -eq 'true') + # Exception: CI, Windows Sandbox + $exception = $env:CI -or ($env:USERNAME -eq 'WDAGUtilityAccount') if (!$exception) { Deny-Install 'Running the installer as administrator is disabled by default, see https://github.com/ScoopInstaller/Install#for-admin for details.' } @@ -575,10 +607,10 @@ function Test-CommandAvailable { function Install-Scoop { Write-InstallInfo 'Initializing...' - # Validate install parameters - Test-ValidateParameter # Check prerequisites Test-Prerequisite + # Validate install parameters + Test-ValidateParameter # Enable TLS 1.2 Optimize-SecurityProtocol @@ -680,12 +712,36 @@ function Write-DebugInfo { Write-Verbose "SCOOP_CONFIG_HOME: $SCOOP_CONFIG_HOME" } +function Test-ShouldRunInstall { + param( + [String] $InvocationName + ) + + # not dot-sourced, run the install flow + if ($InvocationName -ne '.') { + return $true + } + + # dot-sourced, but not in CI, don't run the install flow + if (-not $env:CI) { + return $false + } + + # dot-sourced, in CI, determined by env SCOOP_NOINSTALL + $should = $true + if ($null -ne $env:SCOOP_NOINSTALL) { + $value = $env:SCOOP_NOINSTALL.ToString().Trim().ToLowerInvariant() + if ($value -in @('1', 'true', 'yes', 'on')) { + $should = $false + } + } + + return $should +} + # Prepare variables $IS_EXECUTED_FROM_IEX = ($null -eq $MyInvocation.MyCommand.Path) -# Abort when the language mode is restricted -Test-LanguageMode - # Scoop root directory $SCOOP_DIR = $ScoopDir, $env:SCOOP, "$env:USERPROFILE\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1 # Scoop global apps directory @@ -709,14 +765,23 @@ $SCOOP_MAIN_BUCKET_REPO = 'https://github.com/ScoopInstaller/Main/archive/master $SCOOP_PACKAGE_GIT_REPO = 'https://github.com/ScoopInstaller/Scoop.git' $SCOOP_MAIN_BUCKET_GIT_REPO = 'https://github.com/ScoopInstaller/Main.git' -# Quit if anything goes wrong -$oldErrorActionPreference = $ErrorActionPreference -$ErrorActionPreference = 'Stop' - -# Logging debug info -Write-DebugInfo $PSBoundParameters -# Bootstrap function -Install-Scoop - -# Reset $ErrorActionPreference to original value -$ErrorActionPreference = $oldErrorActionPreference +# The install flow triggers only when the script is executed directly, but +# not when dot-sourced. Dot-sourcing the installer will not trigger the +# installation, and only the functions will be loaded, e.g., for testing. +# Downstreams can call `Install-Scoop` explicitly to start the installation. +# In CI, dot-sourced execution is allowed by default as GitHub Actions executors +# run the job step in a dot-sourced session. To disable the install flow in CI, +# set `SCOOP_NOINSTALL=true` to force dot-sourced CI sessions to only +# load functions. +$shouldRunInstall = Test-ShouldRunInstall -InvocationName $MyInvocation.InvocationName +if ($shouldRunInstall) { + $oldErrorActionPreference = $ErrorActionPreference + try { + $ErrorActionPreference = 'Stop' + Test-LanguageMode + Write-DebugInfo $PSBoundParameters + Install-Scoop + } finally { + $ErrorActionPreference = $oldErrorActionPreference + } +} diff --git a/scoop.json b/scoop.json index a012c5e8..74082dd0 100644 --- a/scoop.json +++ b/scoop.json @@ -1,61 +1,61 @@ { - "buckets": [ - { - "Name": "main", - "Source": "https://github.com/ScoopInstaller/Main.git", - "Updated": "2026-03-01T20:30:21+00:00", - "Manifests": 1449 - } - ], "apps": [ { + "Updated": "2026-08-31T02:27:08.6767247+00:00", "Info": "", - "Name": "7zip", - "Updated": "2026-03-02T00:11:42.5461877+00:00", "Source": "main", - "Version": "26.00" + "Name": "7zip", + "Version": "26.02" }, { + "Updated": "2026-08-31T02:27:21.6952543+00:00", "Info": "", - "Name": "gcc", - "Updated": "2026-03-02T00:11:55.5410061+00:00", "Source": "main", + "Name": "gcc", "Version": "15.2.0" }, { + "Updated": "2026-08-31T02:28:22.5891156+00:00", "Info": "", - "Name": "go", - "Updated": "2026-03-02T00:12:48.7592051+00:00", "Source": "main", - "Version": "1.26.0" + "Name": "go", + "Version": "1.27.0" }, { + "Updated": "2026-08-31T02:28:22.8976333+00:00", "Info": "", - "Name": "jq", - "Updated": "2026-03-02T00:12:49.0700649+00:00", "Source": "main", - "Version": "1.8.1" + "Name": "jq", + "Version": "1.8.2" }, { + "Updated": "2026-08-31T02:29:17.4145778+00:00", "Info": "", - "Name": "mingw", - "Updated": "2026-03-02T00:13:39.1655567+00:00", "Source": "main", - "Version": "15.2.0-rt_v13-rev1" + "Name": "mingw", + "Version": "16.2.0-rt_v14-rev1" }, { + "Updated": "2026-08-31T02:29:29.1451328+00:00", "Info": "", - "Name": "nodejs", - "Updated": "2026-03-02T00:13:47.8899354+00:00", "Source": "main", - "Version": "25.7.0" + "Name": "nodejs", + "Version": "26.8.1" }, { + "Updated": "2026-08-31T02:29:29.8866633+00:00", "Info": "", - "Name": "task", - "Updated": "2026-03-02T00:13:49.2456734+00:00", "Source": "main", - "Version": "3.48.0" + "Name": "task", + "Version": "3.53.1" + } + ], + "buckets": [ + { + "Name": "main", + "Source": "https://github.com/ScoopInstaller/Main.git", + "Updated": "2026-08-31T01:08:29+00:00", + "Manifests": 1640 } ] }