Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $command_name = 'muse' $launcher_url = if ([string]::IsNullOrWhiteSpace($env:MUSE_LAUNCHER_URL)) { 'https://api.meta.ai/muse-launcher.ps1' } else { $env:MUSE_LAUNCHER_URL } $install_dir = '' $launcher = '' $shim = '' $launcher_tmp = '' function Die { param([string]$Message) [Console]::Error.WriteLine("${command_name}: $Message") exit 1 } function Display-Path { param([string]$Path) $profile_dir = $env:USERPROFILE if (-not [string]::IsNullOrWhiteSpace($profile_dir) -and $Path.StartsWith("$profile_dir\", [System.StringComparison]::OrdinalIgnoreCase)) { return '~\' + $Path.Substring($profile_dir.Length + 1) } return $Path } function Cleanup { if (-not [string]::IsNullOrWhiteSpace($script:launcher_tmp)) { Remove-Item -LiteralPath $script:launcher_tmp -Force -ErrorAction SilentlyContinue } } function Sha256-File { param([string]$Path) return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() } function Read-ResponseHeader { param($Response, [string]$Name) foreach ($key in $Response.Headers.Keys) { if ($key -ine $Name) { continue } $value = $Response.Headers[$key] if ($value -is [array]) { $value = $value[-1] } return ([string]$value).Trim() } return '' } function Assert-HttpsUri { param([string]$Uri) $parsed = $null if (-not [Uri]::TryCreate($Uri, [UriKind]::Absolute, [ref]$parsed) -or $parsed.Scheme -ne 'https') { Die "refusing to fetch a launcher over a non-https URL: $Uri" } } function Require-Tls12 { $protocols = [System.Net.SecurityProtocolType]::Tls12 if ([Enum]::IsDefined([System.Net.SecurityProtocolType], 'Tls13')) { $protocols = $protocols -bor [System.Net.SecurityProtocolType]::Tls13 } [System.Net.ServicePointManager]::SecurityProtocol = $protocols } function Resolve-FinalUri { param($Response, [string]$RequestedUri) $base = $Response.BaseResponse if ($null -ne $base.PSObject.Properties['ResponseUri']) { return [string]$base.ResponseUri } if ($null -ne $base.PSObject.Properties['RequestMessage'] -and $null -ne $base.RequestMessage) { return [string]$base.RequestMessage.RequestUri } return $RequestedUri } function Fetch-Launcher { param([string]$Uri, [string]$Destination) Assert-HttpsUri -Uri $Uri $request = @{ UseBasicParsing = $true Uri = $Uri MaximumRedirection = 3 TimeoutSec = 60 OutFile = $Destination PassThru = $true } $response = Invoke-WebRequest @request Assert-HttpsUri -Uri (Resolve-FinalUri -Response $response -RequestedUri $Uri) return $response } function Test-LauncherSyntax { param([string]$Path) $tokens = $null $errors = $null [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$tokens, [ref]$errors) | Out-Null return ($null -eq $errors -or $errors.Count -eq 0) } function Write-Shim { $powershell_exe = '"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"' $lines = @( '@echo off', 'setlocal', "$powershell_exe -NoProfile -ExecutionPolicy Bypass -File `"%~dp0.muse-launcher.ps1`" %*", 'exit /b %ERRORLEVEL%' ) $content = ($lines -join "`r`n") + "`r`n" if ((Test-Path -LiteralPath $script:shim -PathType Leaf) -and [IO.File]::ReadAllText($script:shim) -ceq $content) { return } [IO.File]::WriteAllText($script:shim, $content) } function Invoke-LauncherInstall { $previous_install = $env:MUSE_LAUNCHER_INSTALL $previous_preference = $ErrorActionPreference $env:MUSE_LAUNCHER_INSTALL = '1' $ErrorActionPreference = 'Continue' try { & $script:shim } finally { $ErrorActionPreference = $previous_preference $env:MUSE_LAUNCHER_INSTALL = $previous_install } if ($LASTEXITCODE -ne 0) { Die "the launcher failed to install $command_name (exit code $LASTEXITCODE)" } } function Path-Contains { param([string]$PathValue, [string]$Entry) if ([string]::IsNullOrWhiteSpace($PathValue)) { return $false } $needle = $Entry.TrimEnd('\') foreach ($segment in $PathValue.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)) { if ($segment.Trim().TrimEnd('\') -ieq $needle) { return $true } } return $false } function Configure-Path { if (Path-Contains -PathValue $env:Path -Entry $script:install_dir) { return $true } $shown = Display-Path $script:install_dir $manual_line = '$env:Path = "{0};$env:Path"' -f $script:install_dir if (-not [string]::IsNullOrWhiteSpace($env:MUSE_NO_MODIFY_PATH)) { Write-Host '' Write-Host "$shown is not on your PATH (skipped by MUSE_NO_MODIFY_PATH)." Write-Host "To run $command_name by name, add it yourself:" Write-Host " $manual_line" return $false } $user_path = [Environment]::GetEnvironmentVariable('Path', 'User') Write-Host '' if (Path-Contains -PathValue $user_path -Entry $script:install_dir) { Write-Host "A PATH entry for $shown is already set for your account." } else { $new_user_path = if ([string]::IsNullOrWhiteSpace($user_path)) { $script:install_dir } else { "$script:install_dir;$user_path" } try { [Environment]::SetEnvironmentVariable('Path', $new_user_path, 'User') } catch { Write-Host "$shown is not on your PATH and it could not be updated. Add it yourself:" Write-Host " $manual_line" return $false } Write-Host "Added $shown to PATH for your account." } $env:Path = "$script:install_dir;$env:Path" Write-Host 'Open a new terminal, or start in this one by running:' Write-Host " $manual_line" Write-Host "Then run $command_name" return $false } function Resolve-InstallDir { if (-not [string]::IsNullOrWhiteSpace($env:MUSE_INSTALL_DIR)) { return $env:MUSE_INSTALL_DIR } if ([string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) { Die 'LOCALAPPDATA is not set' } return (Join-Path $env:LOCALAPPDATA "Programs\$command_name") } function Muse-Install { $on_windows = if (Test-Path Variable:IsWindows) { $IsWindows } else { $true } if (-not $on_windows) { Die 'installer.ps1 supports Windows only; use installer.sh on macOS or Linux' } if ($PSVersionTable.PSVersion.Major -lt 5) { Die 'PowerShell 5.1 or newer is required' } $script:install_dir = Resolve-InstallDir if ($script:install_dir -eq [IO.Path]::GetPathRoot($script:install_dir)) { Die "refusing to install into $script:install_dir" } $script:launcher = Join-Path $script:install_dir '.muse-launcher.ps1' $script:shim = Join-Path $script:install_dir "$command_name.cmd" New-Item -ItemType Directory -Force -Path $script:install_dir | Out-Null Require-Tls12 $suffix = [Guid]::NewGuid().ToString('N').Substring(0, 6) $script:launcher_tmp = Join-Path $script:install_dir ".$command_name-launcher.$suffix" $response = Fetch-Launcher -Uri $launcher_url -Destination $script:launcher_tmp if (-not (Test-LauncherSyntax -Path $script:launcher_tmp)) { Die 'downloaded launcher is invalid' } $advertised = (Read-ResponseHeader -Response $response -Name 'x-content-sha256').ToLowerInvariant() if ($advertised -match '^[0-9a-f]{64}$') { if ((Sha256-File $script:launcher_tmp) -cne $advertised) { Die 'downloaded launcher failed checksum verification' } } if ((Test-Path -LiteralPath $script:launcher -PathType Leaf) -and (Sha256-File $script:launcher) -ceq (Sha256-File $script:launcher_tmp)) { Remove-Item -LiteralPath $script:launcher_tmp -Force } else { Move-Item -LiteralPath $script:launcher_tmp -Destination $script:launcher -Force } $script:launcher_tmp = '' Remove-Item -LiteralPath (Join-Path $script:install_dir "$command_name.ps1") ` -Force -ErrorAction SilentlyContinue Write-Shim Invoke-LauncherInstall $stale = @( (Join-Path $script:install_dir '.muse-updater'), (Join-Path $script:install_dir '.muse-install-lock') ) Remove-Item -LiteralPath $stale -Recurse -Force -ErrorAction SilentlyContinue if ($env:MUSE_UPGRADE_MODE -ne '1') { Write-Host "Installed $command_name to $(Display-Path $script:shim)" if (Configure-Path) { Write-Host '' Write-Host "To get started, run $command_name" } } } try { Muse-Install } finally { Cleanup }