公开 版本 1.0

scoop安装脚本

kuma
kuma
11 次浏览
2025-12-06
描述

kuma

提示词内容
<#
.SYNOPSIS
    生产级 Scoop 环境一键部署脚本(支持中断恢复)

.DESCRIPTION
    功能特性:
    1. 自动安装 Scoop(如未安装)
    2. 优先安装核心依赖(Git, 7zip, aria2)
    3. 批量添加 buckets(含错误处理和重试机制)
    4. 按清单安装应用(最小系统必备 + 个人使用)
    5. 完整的日志记录和错误追踪
    6. 支持中断后重新执行(幂等性设计)
    7. 自动权限检查和环境变量刷新
    8. 交互式选择是否启用 aria2c 下载加速

.PARAMETER LogPath
    日志文件保存路径,默认为 $env:TEMP\scoop-init.log

.PARAMETER SkipAdminCheck
    跳过管理员权限检查(不推荐)

.PARAMETER UseAria2
    是否启用 aria2c 加速下载。
    - 如果指定此参数,则自动启用
    - 如果不指定,脚本会交互式询问用户

.EXAMPLE
    .\scoop-init.ps1
    # 执行时会询问是否启用 aria2c
    
.EXAMPLE
    .\scoop-init.ps1 -UseAria2
    # 直接启用 aria2c,不询问

.EXAMPLE
    .\scoop-init.ps1 -UseAria2:$false
    # 直接禁用 aria2c,不询问

.EXAMPLE
    .\scoop-init.ps1 -LogPath "D:\logs\scoop-init.log"

.NOTES
    Author: Your Name
    Version: 2.1
    LastModified: 2025-12-06
#>

[CmdletBinding()]
param(
    [string]$LogPath = "$env:TEMP\scoop-init-$(Get-Date -Format 'yyyyMMdd-HHmmss').log",
    [switch]$SkipAdminCheck,
    [Parameter(Mandatory=$false)]
    [bool]$UseAria2 = $null
)

# ============================================
# 全局配置
# ============================================
$ErrorActionPreference = 'Continue'
$global:FailedOperations = @()
$global:SuccessCount = 0
$global:SkipCount = 0
$global:EnableAria2 = $null  # 用于存储用户选择

# ============================================
# 日志与输出函数
# ============================================
function Write-Log {
    param(
        [string]$Message,
        [ValidateSet('INFO', 'SUCCESS', 'WARNING', 'ERROR', 'SKIP')]
        [string]$Level = 'INFO'
    )
    
    $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    $logMessage = "[$timestamp] [$Level] $Message"
    
    # 输出到文件
    Add-Content -Path $LogPath -Value $logMessage
    
    # 输出到控制台(带颜色)
    $colors = @{
        'INFO'    = 'Cyan'
        'SUCCESS' = 'Green'
        'WARNING' = 'Yellow'
        'ERROR'   = 'Red'
        'SKIP'    = 'DarkGray'
    }
    Write-Host $logMessage -ForegroundColor $colors[$Level]
}

function Write-SectionHeader {
    param([string]$Title)
    $separator = "=" * 60
    Write-Log "`n$separator" -Level INFO
    Write-Log "  $Title" -Level INFO
    Write-Log "$separator" -Level INFO
}

# ============================================
# 用户交互函数
# ============================================
function Get-UserConfirmation {
    <#
    .SYNOPSIS
        获取用户的是/否确认
    .DESCRIPTION
        显示提示信息并等待用户输入 Y/N
    #>
    param(
        [Parameter(Mandatory)]
        [string]$Prompt,
        
        [string]$DefaultChoice = 'Y'
    )
    
    $choices = if ($DefaultChoice -eq 'Y') { '[Y/n]' } else { '[y/N]' }
    
    do {
        Write-Host "`n$Prompt $choices " -ForegroundColor Yellow -NoNewline
        $response = Read-Host
        
        # 如果用户直接回车,使用默认值
        if ([string]::IsNullOrWhiteSpace($response)) {
            $response = $DefaultChoice
        }
        
        $response = $response.Trim().ToUpper()
        
        if ($response -eq 'Y' -or $response -eq 'YES') {
            return $true
        } elseif ($response -eq 'N' -or $response -eq 'NO') {
            return $false
        } else {
            Write-Host "无效输入,请输入 Y 或 N" -ForegroundColor Red
        }
    } while ($true)
}

function Request-Aria2Preference {
    <#
    .SYNOPSIS
        询问用户是否启用 aria2c 加速
    #>
    Write-Host "`n" -NoNewline
    Write-Host "╔════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
    Write-Host "║            是否启用 aria2c 多线程下载加速?               ║" -ForegroundColor Cyan
    Write-Host "╠════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
    Write-Host "║  优点:                                                     ║" -ForegroundColor Cyan
    Write-Host "║    • 显著提升大文件下载速度(最多 16 线程)               ║" -ForegroundColor Green
    Write-Host "║    • 支持断点续传                                          ║" -ForegroundColor Green
    Write-Host "║    • 自动重试失败的下载                                    ║" -ForegroundColor Green
    Write-Host "║                                                            ║" -ForegroundColor Cyan
    Write-Host "║  缺点:                                                     ║" -ForegroundColor Cyan
    Write-Host "║    • 需额外安装 aria2c 工具(约 5MB)                     ║" -ForegroundColor Yellow
    Write-Host "║    • 某些网站可能限制多线程下载                           ║" -ForegroundColor Yellow
    Write-Host "║                                                            ║" -ForegroundColor Cyan
    Write-Host "║  推荐: 如果网络环境良好,建议启用以加速安装              ║" -ForegroundColor White
    Write-Host "╚════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
    
    return Get-UserConfirmation -Prompt "是否启用 aria2c 加速?" -DefaultChoice 'Y'
}

# ============================================
# 环境检查函数
# ============================================
function Test-AdminPrivilege {
    <#
    .SYNOPSIS
        检查当前 PowerShell 会话是否具有管理员权限
    #>
    $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($identity)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Test-PowerShellVersion {
    <#
    .SYNOPSIS
        检查 PowerShell 版本是否满足要求(>= 5.0)
    #>
    $version = $PSVersionTable.PSVersion
    if ($version.Major -lt 5) {
        Write-Log "PowerShell 版本过低 ($version),需要 5.0 及以上版本" -Level ERROR
        return $false
    }
    Write-Log "PowerShell 版本检查通过: $version" -Level SUCCESS
    return $true
}

function Refresh-EnvironmentPath {
    <#
    .SYNOPSIS
        刷新当前会话的 PATH 环境变量
    #>
    $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "User") + ";" + 
                [System.Environment]::GetEnvironmentVariable("Path", "Machine")
    Write-Log "环境变量已刷新" -Level INFO
}

# ============================================
# Scoop 安装与配置函数
# ============================================
function Install-ScoopIfNeeded {
    <#
    .SYNOPSIS
        检查并安装 Scoop(如未安装)
    #>
    if (Get-Command scoop -ErrorAction SilentlyContinue) {
        Write-Log "Scoop 已安装,跳过安装步骤" -Level SKIP
        $global:SkipCount++
        return $true
    }

    Write-Log "未检测到 Scoop,开始安装..." -Level INFO

    try {
        # 设置执行策略
        Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -Force -ErrorAction Stop
        Write-Log "执行策略已设置为 RemoteSigned" -Level SUCCESS

        # 使用官方安装脚本
        Write-Log "正在下载并执行 Scoop 安装脚本..." -Level INFO
        Invoke-RestMethod -Uri 'https://get.scoop.sh' | Invoke-Expression
        
        # 验证安装
        Refresh-EnvironmentPath
        if (Get-Command scoop -ErrorAction SilentlyContinue) {
            Write-Log "Scoop 安装成功" -Level SUCCESS
            $global:SuccessCount++
            return $true
        } else {
            throw "安装完成但无法找到 scoop 命令"
        }
    } catch {
        Write-Log "Scoop 安装失败: $($_.Exception.Message)" -Level ERROR
        $global:FailedOperations += "安装 Scoop"
        return $false
    }
}

function Install-CoreDependencies {
    <#
    .SYNOPSIS
        安装核心依赖工具(Git, 7zip, 可选的 aria2)
    #>
    param(
        [Parameter(Mandatory)]
        [bool]$InstallAria2
    )
    
    # 基础依赖(必装)
    $coreDeps = @('git', '7zip')
    
    # 根据用户选择决定是否安装 aria2
    if ($InstallAria2) {
        $coreDeps += 'aria2'
        Write-Log "用户已选择启用 aria2c 加速" -Level INFO
    } else {
        Write-Log "用户已选择不使用 aria2c 加速" -Level INFO
    }
    
    foreach ($dep in $coreDeps) {
        try {
            # 检查是否已安装
            $installed = scoop list | Select-String "^\s*$dep\s" -ErrorAction SilentlyContinue
            
            if ($installed) {
                Write-Log "$dep 已安装,跳过" -Level SKIP
                $global:SkipCount++
                continue
            }

            Write-Log "正在安装 $dep ..." -Level INFO
            scoop install $dep 2>&1 | Out-Null
            
            # 验证安装
            $verified = scoop list | Select-String "^\s*$dep\s"
            if ($verified) {
                Write-Log "$dep 安装成功" -Level SUCCESS
                $global:SuccessCount++
            } else {
                throw "安装后验证失败"
            }
        } catch {
            Write-Log "$dep 安装失败: $($_.Exception.Message)" -Level ERROR
            $global:FailedOperations += "安装 $dep"
        }
    }

    # Git 安装后刷新环境变量
    if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
        Refresh-EnvironmentPath
    }

    # 配置 aria2(如果用户选择启用且安装成功)
    if ($InstallAria2) {
        try {
            # 检查 aria2 是否安装成功
            $aria2Installed = scoop list | Select-String "^\s*aria2\s"
            
            if ($aria2Installed) {
                scoop config aria2-enabled true 2>&1 | Out-Null
                scoop config aria2-warning-enabled false 2>&1 | Out-Null
                scoop config aria2-max-connection-per-server 16 2>&1 | Out-Null
                scoop config aria2-split 16 2>&1 | Out-Null
                scoop config aria2-min-split-size '1M' 2>&1 | Out-Null
                Write-Log "aria2c 下载加速已启用(16 线程)" -Level SUCCESS
            } else {
                Write-Log "aria2 未安装成功,跳过配置" -Level WARNING
            }
        } catch {
            Write-Log "aria2 配置失败(不影响主流程): $($_.Exception.Message)" -Level WARNING
        }
    } else {
        # 确保 aria2 被禁用
        try {
            scoop config aria2-enabled false 2>&1 | Out-Null
            Write-Log "aria2c 已禁用,使用默认下载方式" -Level INFO
        } catch {
            # 忽略配置失败(可能 Scoop 是首次安装,配置文件不存在)
        }
    }
}

# ============================================
# Bucket 管理函数
# ============================================
function Add-ScoopBucketSafe {
    <#
    .SYNOPSIS
        安全地添加 Scoop bucket(含重试机制)
    #>
    param(
        [Parameter(Mandatory)]
        [string]$Name,
        
        [Parameter(Mandatory)]
        [string]$Url,
        
        [int]$MaxRetries = 2
    )

    try {
        # 检查是否已存在
        $exists = scoop bucket list 2>&1 | Select-String "^\s*$Name\s" -ErrorAction SilentlyContinue
        
        if ($exists) {
            Write-Log "Bucket '$Name' 已存在,跳过" -Level SKIP
            $global:SkipCount++
            return $true
        }

        # 尝试添加(含重试)
        for ($i = 1; $i -le $MaxRetries; $i++) {
            try {
                Write-Log "正在添加 Bucket '$Name' (尝试 $i/$MaxRetries)..." -Level INFO
                scoop bucket add $Name $Url 2>&1 | Out-Null
                
                # 验证添加是否成功
                $verified = scoop bucket list 2>&1 | Select-String "^\s*$Name\s"
                if ($verified) {
                    Write-Log "Bucket '$Name' 添加成功" -Level SUCCESS
                    $global:SuccessCount++
                    return $true
                }
            } catch {
                if ($i -eq $MaxRetries) {
                    throw
                }
                Write-Log "添加失败,2 秒后重试..." -Level WARNING
                Start-Sleep -Seconds 2
            }
        }
        
        throw "达到最大重试次数"
    } catch {
        Write-Log "Bucket '$Name' 添加失败: $($_.Exception.Message)" -Level ERROR
        $global:FailedOperations += "添加 Bucket: $Name"
        return $false
    }
}

# ============================================
# 应用安装函数
# ============================================
function Install-ScoopAppSafe {
    <#
    .SYNOPSIS
        安全地安装 Scoop 应用(含错误处理)
    #>
    param(
        [Parameter(Mandatory)]
        [string]$Name,
        
        [Parameter(Mandatory)]
        [string]$Bucket
    )

    $fullName = "$Bucket/$Name"

    try {
        # 检查是否已安装
        $installed = scoop list 2>&1 | Select-String "^\s*$Name\s" -ErrorAction SilentlyContinue
        
        if ($installed) {
            Write-Log "$fullName 已安装,跳过" -Level SKIP
            $global:SkipCount++
            return $true
        }

        Write-Log "正在安装 $fullName ..." -Level INFO
        
        # 捕获安装输出
        $output = scoop install $fullName 2>&1
        
        # 检查是否包含警告信息(某些应用可能需要额外配置)
        if ($output -match "WARN") {
            Write-Log "安装完成但有警告: $($output -join ' ')" -Level WARNING
        }
        
        # 验证安装
        $verified = scoop list 2>&1 | Select-String "^\s*$Name\s"
        if ($verified) {
            Write-Log "$fullName 安装成功" -Level SUCCESS
            $global:SuccessCount++
            return $true
        } else {
            throw "安装后验证失败"
        }
    } catch {
        Write-Log "$fullName 安装失败: $($_.Exception.Message)" -Level ERROR
        $global:FailedOperations += "安装应用: $fullName"
        return $false
    }
}

# ============================================
# 主流程
# ============================================
function Main {
    $startTime = Get-Date
    
    Write-Log "========================================" -Level INFO
    Write-Log "  Scoop 环境初始化脚本 v2.1" -Level INFO
    Write-Log "  日志文件: $LogPath" -Level INFO
    Write-Log "========================================" -Level INFO

    # 0. 确定 aria2 使用偏好
    Write-SectionHeader "步骤 0: 配置下载加速"
    
    if ($PSBoundParameters.ContainsKey('UseAria2')) {
        # 用户通过命令行参数明确指定了
        $global:EnableAria2 = $UseAria2
        if ($UseAria2) {
            Write-Log "通过参数指定:启用 aria2c 加速" -Level INFO
        } else {
            Write-Log "通过参数指定:禁用 aria2c 加速" -Level INFO
        }
    } else {
        # 交互式询问用户
        $global:EnableAria2 = Request-Aria2Preference
        Write-Log "用户选择:$(if ($global:EnableAria2) { '启用' } else { '禁用' }) aria2c 加速" -Level INFO
    }

    # 1. 环境检查
    Write-SectionHeader "步骤 1: 环境检查"
    
    if (-not (Test-PowerShellVersion)) {
        Write-Log "环境检查失败,脚本终止" -Level ERROR
        return
    }

    if (-not $SkipAdminCheck) {
        if (Test-AdminPrivilege) {
            Write-Log "检测到管理员权限,某些应用可能需要此权限" -Level SUCCESS
        } else {
            Write-Log "当前未以管理员身份运行,部分应用安装可能需要提升权限" -Level WARNING
        }
    }

    # 2. 安装 Scoop
    Write-SectionHeader "步骤 2: 安装 Scoop"
    if (-not (Install-ScoopIfNeeded)) {
        Write-Log "Scoop 安装失败,无法继续" -Level ERROR
        return
    }

    # 3. 安装核心依赖
    Write-SectionHeader "步骤 3: 安装核心依赖工具"
    Install-CoreDependencies -InstallAria2 $global:EnableAria2

    # 验证 Git 是否可用
    if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
        Write-Log "Git 未正确安装,无法添加 buckets" -Level ERROR
        Write-Log "请手动运行 'scoop install git' 后重新执行本脚本" -Level ERROR
        return
    }

    # 4. 添加 Buckets
    Write-SectionHeader "步骤 4: 添加 Buckets"
    
    $buckets = @(
        @{ Name = 'main';       Url = 'https://github.com/ScoopInstaller/Main.git' }
        @{ Name = 'extras';     Url = 'https://github.com/ScoopInstaller/Extras' }
        @{ Name = 'versions';   Url = 'https://github.com/ScoopInstaller/Versions' }
        @{ Name = 'apps';       Url = 'https://github.com/kkzzhizhou/scoop-apps' }
        @{ Name = 'atm';        Url = 'https://github.com/zhaochengcube/scoop-atm' }
        @{ Name = 'extras-cn';  Url = 'https://github.com/Scoopforge/Extras-CN' }
        @{ Name = 'lemon';      Url = 'https://github.com/hoilc/scoop-lemon' }
        @{ Name = 'MorFans-apt'; Url = 'https://github.com/Paxxs/Cluttered-bucket.git' }
    )

    foreach ($bucket in $buckets) {
        Add-ScoopBucketSafe -Name $bucket.Name -Url $bucket.Url
    }

    # 5. 安装应用
    Write-SectionHeader "步骤 5: 安装应用清单"

    # 最小系统必备
    $coreApps = @(
        @{ Name = '7zip';                  Bucket = 'main'   }
        @{ Name = 'aria2';                 Bucket = 'main'   }
        @{ Name = 'oh-my-posh';            Bucket = 'main'   }
        @{ Name = 'ddu';                   Bucket = 'extras' }
        @{ Name = 'potplayer';             Bucket = 'extras' }
        @{ Name = 'office-tool-plus';      Bucket = 'extras' }
        @{ Name = 'libreoffice';           Bucket = 'extras' }
        @{ Name = 'googlechrome';          Bucket = 'extras' }
        @{ Name = 'spacesniffer';          Bucket = 'extras' }
        @{ Name = 'ezunlock';              Bucket = 'extras' }
        @{ Name = 'autoruns';              Bucket = 'apps'   }
        @{ Name = 'sparkle';               Bucket = 'lemon'  }
        @{ Name = 'notepadplusplus';       Bucket = 'extras' }
        @{ Name = 'wechat';                Bucket = 'extras' }
        @{ Name = 'qq';                    Bucket = 'extras' }
        @{ Name = 'baidu-net-disk';        Bucket = 'apps'   }
        @{ Name = 'defender-control';      Bucket = 'apps'   }
        @{ Name = 'defender-remover';      Bucket = 'apps'   }
        @{ Name = 'listary';               Bucket = 'extras' }
        @{ Name = 'hibit-uninstaller';     Bucket = 'extras' }
        @{ Name = 'snipaste';              Bucket = 'extras' }
        @{ Name = 'idm';                   Bucket = 'apps'   }
        @{ Name = 'pot';                   Bucket = 'lemon'  }
        @{ Name = 'everything';            Bucket = 'extras' }
        @{ Name = 'dismplusplus';          Bucket = 'extras' }
        @{ Name = 'localsend';             Bucket = 'extras' }
        @{ Name = 'JetBrainsMono-NF-Mono'; Bucket = 'apps'   }
        @{ Name = 'nilesoft-shell';        Bucket = 'extras' }
        @{ Name = 'diskgenius';            Bucket = 'extras' }
        @{ Name = 'pwsh';                  Bucket = 'main'   }
    )

    # 个人使用清单
    $personalApps = @(
        @{ Name = 'battlenet';         Bucket = 'apps'    }
        @{ Name = 'steam';             Bucket = 'versions'}
        @{ Name = 'bilibili';          Bucket = 'apps'    }
        @{ Name = 'docker-desktop';    Bucket = 'apps'    }
        @{ Name = 'vmware';            Bucket = 'apps'    }
        @{ Name = 'jetbrains-toolbox'; Bucket = 'apps'    }
        @{ Name = 'LXGWWenKaiScreenR'; Bucket = 'apps'    }
        @{ Name = 'obsidian';          Bucket = 'extras'  }
        @{ Name = 'vscode';            Bucket = 'extras'  }
        @{ Name = 'finalshell';        Bucket = 'apps'    }
        @{ Name = 'apifox';            Bucket = 'extras'  }
        @{ Name = 'wireshark';         Bucket = 'extras'  }
        @{ Name = 'telegram';          Bucket = 'apps'    }
        @{ Name = 'cherry-studio';     Bucket = 'extras'  }
        @{ Name = 'ollama';            Bucket = 'main'    }
        @{ Name = 'mqttx';             Bucket = 'extras'  }
        @{ Name = 'clangd';            Bucket = 'main'    }
        @{ Name = 'cmake';             Bucket = 'main'    }
        @{ Name = 'gcc-arm-none-eabi'; Bucket = 'extras'  }
        @{ Name = 'mingw';             Bucket = 'main'    }
        @{ Name = 'ninja';             Bucket = 'main'    }
        @{ Name = 'openocd';           Bucket = 'main'    }
        @{ Name = 'vcpkg';             Bucket = 'main'    }
    )

    # 合并应用清单
    $allApps = $coreApps + $personalApps

    Write-Log "开始安装 $($allApps.Count) 个应用..." -Level INFO

    foreach ($app in $allApps) {
        Install-ScoopAppSafe -Name $app.Name -Bucket $app.Bucket
    }

    # 6. 生成执行报告
    Write-SectionHeader "执行完成 - 统计报告"
    
    $duration = (Get-Date) - $startTime
    
    Write-Log "执行时长: $($duration.ToString('hh\:mm\:ss'))" -Level INFO
    Write-Log "成功操作: $global:SuccessCount 项" -Level SUCCESS
    Write-Log "跳过操作: $global:SkipCount 项" -Level SKIP
    Write-Log "失败操作: $($global:FailedOperations.Count) 项" -Level $(if ($global:FailedOperations.Count -eq 0) { 'SUCCESS' } else { 'WARNING' })
    Write-Log "aria2c 加速: $(if ($global:EnableAria2) { '已启用' } else { '未启用' })" -Level INFO

    if ($global:FailedOperations.Count -gt 0) {
        Write-Log "`n失败清单:" -Level WARNING
        foreach ($failed in $global:FailedOperations) {
            Write-Log "  - $failed" -Level ERROR
        }
        Write-Log "`n建议: 修复网络/权限问题后重新运行本脚本,已完成的步骤会被自动跳过" -Level INFO
    } else {
        Write-Log "`n🎉 所有操作已成功完成!" -Level SUCCESS
    }

    Write-Log "`n详细日志已保存至: $LogPath" -Level INFO
}

# ============================================
# 脚本入口
# ============================================
try {
    Main
} catch {
    Write-Log "脚本执行出现未预期的错误: $($_.Exception.Message)" -Level ERROR
    Write-Log "堆栈跟踪: $($_.ScriptStackTrace)" -Level ERROR
} finally {
    Write-Log "`n脚本执行结束" -Level INFO
}