1
0

install.ps1 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. #!/usr/bin/env pwsh
  2. param(
  3. # Forces installing the baseline build regardless of what CPU you are actually using.
  4. [Switch]$ForceBaseline = $false,
  5. # Skips adding the bun.exe directory to the user's %PATH%
  6. [Switch]$NoPathUpdate = $false,
  7. # Skips adding the bun to the list of installed programs
  8. [Switch]$NoRegisterInstallation = $false,
  9. # Skips installing powershell completions to your profile
  10. [Switch]$NoCompletions = $false,
  11. # Debugging: Always download with 'Invoke-RestMethod' instead of 'curl.exe'
  12. [Switch]$DownloadWithoutCurl = $false
  13. );
  14. $Version = if ($env:BUN_VERSION) { $env:BUN_VERSION } else { "latest" }
  15. # filter out 32 bit + ARM
  16. if (-not ((Get-CimInstance Win32_ComputerSystem)).SystemType -match "x64-based") {
  17. Write-Output "Install Failed:"
  18. Write-Output "Bun for Windows is currently only available for x86 64-bit Windows.`n"
  19. return 1
  20. }
  21. # This corresponds to .win10_rs5 in build.zig
  22. $MinBuild = 17763;
  23. $MinBuildName = "Windows 10 1809"
  24. $WinVer = [System.Environment]::OSVersion.Version
  25. if ($WinVer.Major -lt 10 -or ($WinVer.Major -eq 10 -and $WinVer.Build -lt $MinBuild)) {
  26. Write-Warning "Bun requires at ${MinBuildName} or newer.`n`nThe install will still continue but it may not work.`n"
  27. return 1
  28. }
  29. $ErrorActionPreference = "Stop"
  30. # These three environment functions are roughly copied from https://github.com/prefix-dev/pixi/pull/692
  31. # They are used instead of `SetEnvironmentVariable` because of unwanted variable expansions.
  32. function Publish-Env {
  33. if (-not ("Win32.NativeMethods" -as [Type])) {
  34. Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"
  35. [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
  36. public static extern IntPtr SendMessageTimeout(
  37. IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
  38. uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
  39. "@
  40. }
  41. $HWND_BROADCAST = [IntPtr] 0xffff
  42. $WM_SETTINGCHANGE = 0x1a
  43. $result = [UIntPtr]::Zero
  44. [Win32.NativeMethods]::SendMessageTimeout($HWND_BROADCAST,
  45. $WM_SETTINGCHANGE,
  46. [UIntPtr]::Zero,
  47. "Environment",
  48. 2,
  49. 5000,
  50. [ref] $result
  51. ) | Out-Null
  52. }
  53. function Write-Env {
  54. param([String]$Key, [String]$Value)
  55. $RegisterKey = Get-Item -Path 'HKCU:'
  56. $EnvRegisterKey = $RegisterKey.OpenSubKey('Environment', $true)
  57. if ($null -eq $Value) {
  58. $EnvRegisterKey.DeleteValue($Key)
  59. } else {
  60. $RegistryValueKind = if ($Value.Contains('%')) {
  61. [Microsoft.Win32.RegistryValueKind]::ExpandString
  62. } elseif ($EnvRegisterKey.GetValue($Key)) {
  63. $EnvRegisterKey.GetValueKind($Key)
  64. } else {
  65. [Microsoft.Win32.RegistryValueKind]::String
  66. }
  67. $EnvRegisterKey.SetValue($Key, $Value, $RegistryValueKind)
  68. }
  69. Publish-Env
  70. }
  71. function Get-Env {
  72. param([String] $Key)
  73. $RegisterKey = Get-Item -Path 'HKCU:'
  74. $EnvRegisterKey = $RegisterKey.OpenSubKey('Environment')
  75. $EnvRegisterKey.GetValue($Key, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
  76. }
  77. # The installation of bun is it's own function so that in the unlikely case the $IsBaseline check fails, we can do a recursive call.
  78. # There are also lots of sanity checks out of fear of anti-virus software or other weird Windows things happening.
  79. function Install-Bun {
  80. param(
  81. [string]$Version,
  82. [bool]$ForceBaseline = $False
  83. );
  84. # if a semver is given, we need to adjust it to this format: bun-v0.0.0
  85. if ($Version -match "^\d+\.\d+\.\d+$") {
  86. $Version = "bun-v$Version"
  87. }
  88. elseif ($Version -match "^v\d+\.\d+\.\d+$") {
  89. $Version = "bun-$Version"
  90. }
  91. $Arch = "x64"
  92. $IsBaseline = $ForceBaseline
  93. if (!$IsBaseline) {
  94. $IsBaseline = !( `
  95. Add-Type -MemberDefinition '[DllImport("kernel32.dll")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);' `
  96. -Name 'Kernel32' -Namespace 'Win32' -PassThru `
  97. )::IsProcessorFeaturePresent(40);
  98. }
  99. $BunRoot = if ($env:BUN_INSTALL) { $env:BUN_INSTALL } else { "${Home}\.bun" }
  100. $BunBin = mkdir -Force "${BunRoot}\bin"
  101. try {
  102. Remove-Item "${BunBin}\bun.exe" -Force
  103. } catch [System.Management.Automation.ItemNotFoundException] {
  104. # ignore
  105. } catch [System.UnauthorizedAccessException] {
  106. $openProcesses = Get-Process -Name bun | Where-Object { $_.Path -eq "${BunBin}\bun.exe" }
  107. if ($openProcesses.Count -gt 0) {
  108. Write-Output "Install Failed - An older installation exists and is open. Please close open Bun processes and try again."
  109. return 1
  110. }
  111. Write-Output "Install Failed - An unknown error occurred while trying to remove the existing installation"
  112. Write-Output $_
  113. return 1
  114. } catch {
  115. Write-Output "Install Failed - An unknown error occurred while trying to remove the existing installation"
  116. Write-Output $_
  117. return 1
  118. }
  119. $Target = "bun-windows-$Arch"
  120. if ($IsBaseline) {
  121. $Target = "bun-windows-$Arch-baseline"
  122. }
  123. $BaseURL = "https://github.com/oven-sh/bun/releases"
  124. $URL = "$BaseURL/$(if ($Version -eq "latest") { "latest/download" } else { "download/$Version" })/$Target.zip"
  125. $ZipPath = "${BunBin}\$Target.zip"
  126. $DisplayVersion = $(
  127. if ($Version -eq "latest") { "Bun" }
  128. elseif ($Version -eq "canary") { "Bun Canary" }
  129. elseif ($Version -match "^bun-v\d+\.\d+\.\d+$") { "Bun $($Version.Substring(4))" }
  130. else { "Bun tag='${Version}'" }
  131. )
  132. $null = mkdir -Force $BunBin
  133. Remove-Item -Force $ZipPath -ErrorAction SilentlyContinue
  134. # curl.exe is faster than PowerShell 5's 'Invoke-WebRequest'
  135. # note: 'curl' is an alias to 'Invoke-WebRequest'. so the exe suffix is required
  136. if (-not $DownloadWithoutCurl) {
  137. curl.exe "-#SfLo" "$ZipPath" "$URL"
  138. }
  139. if ($DownloadWithoutCurl -or ($LASTEXITCODE -ne 0)) {
  140. Write-Warning "The command 'curl.exe $URL -o $ZipPath' exited with code ${LASTEXITCODE}`nTrying an alternative download method..."
  141. try {
  142. # Use Invoke-RestMethod instead of Invoke-WebRequest because Invoke-WebRequest breaks on
  143. # some machines, see
  144. Invoke-RestMethod -Uri $URL -OutFile $ZipPath
  145. } catch {
  146. Write-Output "Install Failed - could not download $URL"
  147. Write-Output "The command 'Invoke-RestMethod $URL -OutFile $ZipPath' exited with code ${LASTEXITCODE}`n"
  148. return 1
  149. }
  150. }
  151. if (!(Test-Path $ZipPath)) {
  152. Write-Output "Install Failed - could not download $URL"
  153. Write-Output "The file '$ZipPath' does not exist. Did an antivirus delete it?`n"
  154. return 1
  155. }
  156. try {
  157. $lastProgressPreference = $global:ProgressPreference
  158. $global:ProgressPreference = 'SilentlyContinue';
  159. Expand-Archive "$ZipPath" "$BunBin" -Force
  160. $global:ProgressPreference = $lastProgressPreference
  161. if (!(Test-Path "${BunBin}\$Target\bun.exe")) {
  162. throw "The file '${BunBin}\$Target\bun.exe' does not exist. Download is corrupt or intercepted Antivirus?`n"
  163. }
  164. } catch {
  165. Write-Output "Install Failed - could not unzip $ZipPath"
  166. Write-Error $_
  167. return 1
  168. }
  169. Move-Item "${BunBin}\$Target\bun.exe" "${BunBin}\bun.exe" -Force
  170. Remove-Item "${BunBin}\$Target" -Recurse -Force
  171. Remove-Item $ZipPath -Force
  172. $BunRevision = "$(& "${BunBin}\bun.exe" --revision)"
  173. if ($LASTEXITCODE -eq 1073741795) { # STATUS_ILLEGAL_INSTRUCTION
  174. if ($IsBaseline) {
  175. Write-Output "Install Failed - bun.exe (baseline) is not compatible with your CPU.`n"
  176. Write-Output "Please open a GitHub issue with your CPU model:`nhttps://github.com/oven-sh/bun/issues/new/choose`n"
  177. return 1
  178. }
  179. Write-Output "Install Failed - bun.exe is not compatible with your CPU. This should have been detected before downloading.`n"
  180. Write-Output "Attempting to download bun.exe (baseline) instead.`n"
  181. Install-Bun -Version $Version -ForceBaseline $True
  182. return 1
  183. }
  184. # '-1073741515' was spotted in the wild, but not clearly documented as a status code:
  185. # https://discord.com/channels/876711213126520882/1149339379446325248/1205194965383250081
  186. # http://community.sqlbackupandftp.com/t/error-1073741515-solved/1305
  187. if (($LASTEXITCODE -eq 3221225781) -or ($LASTEXITCODE -eq -1073741515)) # STATUS_DLL_NOT_FOUND
  188. {
  189. # TODO: as of July 2024, Bun has no external dependencies.
  190. # I want to keep this error message in for a few months to ensure that
  191. # if someone somehow runs into this, it can be reported.
  192. Write-Output "Install Failed - You are missing a DLL required to run bun.exe"
  193. Write-Output "This can be solved by installing the Visual C++ Redistributable from Microsoft:`nSee https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist`nDirect Download -> https://aka.ms/vs/17/release/vc_redist.x64.exe`n`n"
  194. Write-Output "The error above should be unreachable as Bun does not depend on this library. Please comment in https://github.com/oven-sh/bun/issues/8598 or open a new issue.`n`n"
  195. Write-Output "The command '${BunBin}\bun.exe --revision' exited with code ${LASTEXITCODE}`n"
  196. return 1
  197. }
  198. if ($LASTEXITCODE -ne 0) {
  199. Write-Output "Install Failed - could not verify bun.exe"
  200. Write-Output "The command '${BunBin}\bun.exe --revision' exited with code ${LASTEXITCODE}`n"
  201. return 1
  202. }
  203. try {
  204. $env:IS_BUN_AUTO_UPDATE = "1"
  205. # TODO: When powershell completions are added, make this switch actually do something
  206. if ($NoCompletions) {
  207. $env:BUN_NO_INSTALL_COMPLETIONS = "1"
  208. }
  209. # This completions script in general will install some extra stuff, mainly the `bunx` link.
  210. # It also installs completions.
  211. $output = "$(& "${BunBin}\bun.exe" completions 2>&1)"
  212. if ($LASTEXITCODE -ne 0) {
  213. Write-Output $output
  214. Write-Output "Install Failed - could not finalize installation"
  215. Write-Output "The command '${BunBin}\bun.exe completions' exited with code ${LASTEXITCODE}`n"
  216. return 1
  217. }
  218. } catch {
  219. # it is possible on powershell 5 that an error happens, but it is probably fine?
  220. }
  221. $env:IS_BUN_AUTO_UPDATE = $null
  222. $env:BUN_NO_INSTALL_COMPLETIONS = $null
  223. $DisplayVersion = if ($BunRevision -like "*-canary.*") {
  224. "${BunRevision}"
  225. } else {
  226. "$(& "${BunBin}\bun.exe" --version)"
  227. }
  228. $C_RESET = [char]27 + "[0m"
  229. $C_GREEN = [char]27 + "[1;32m"
  230. Write-Output "${C_GREEN}Bun ${DisplayVersion} was installed successfully!${C_RESET}"
  231. Write-Output "The binary is located at ${BunBin}\bun.exe`n"
  232. $hasExistingOther = $false;
  233. try {
  234. $existing = Get-Command bun -ErrorAction
  235. if ($existing.Source -ne "${BunBin}\bun.exe") {
  236. Write-Warning "Note: Another bun.exe is already in %PATH% at $($existing.Source)`nTyping 'bun' in your terminal will not use what was just installed.`n"
  237. $hasExistingOther = $true;
  238. }
  239. } catch {}
  240. if (-not $NoRegisterInstallation) {
  241. $rootKey = $null
  242. try {
  243. $RegistryKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Bun"
  244. $rootKey = New-Item -Path $RegistryKey -Force
  245. New-ItemProperty -Path $RegistryKey -Name "DisplayName" -Value "Bun" -PropertyType String -Force | Out-Null
  246. New-ItemProperty -Path $RegistryKey -Name "InstallLocation" -Value "${BunRoot}" -PropertyType String -Force | Out-Null
  247. New-ItemProperty -Path $RegistryKey -Name "DisplayIcon" -Value $BunBin\bun.exe -PropertyType String -Force | Out-Null
  248. New-ItemProperty -Path $RegistryKey -Name "UninstallString" -Value "powershell -c `"& `'$BunRoot\uninstall.ps1`' -PauseOnError`" -ExecutionPolicy Bypass" -PropertyType String -Force | Out-Null
  249. } catch {
  250. if ($rootKey -ne $null) {
  251. Remove-Item -Path $RegistryKey -Force
  252. }
  253. }
  254. }
  255. if(!$hasExistingOther) {
  256. # Only try adding to path if there isn't already a bun.exe in the path
  257. $Path = (Get-Env -Key "Path") -split ';'
  258. if ($Path -notcontains $BunBin) {
  259. if (-not $NoPathUpdate) {
  260. $Path += $BunBin
  261. Write-Env -Key 'Path' -Value ($Path -join ';')
  262. $env:PATH = $Path;
  263. } else {
  264. Write-Output "Skipping adding '${BunBin}' to the user's %PATH%`n"
  265. }
  266. }
  267. Write-Output "To get started, restart your terminal/editor, then type `"bun`"`n"
  268. }
  269. $LASTEXITCODE = 0;
  270. }
  271. Install-Bun -Version $Version -ForceBaseline $ForceBaseline