private/远程安装脚本-Win7.ps1

# 远程安装脚本(Win7 适配版)
# 兼容 PowerShell 2.0 / Windows 7
# 此文件通过主脚本上传到远程主机执行,__占位符__ 会在上传前被替换为实际值。

$ErrorActionPreference = 'Stop'

# PowerShell 版本自检
if ($PSVersionTable.PSVersion.Major -lt 2) {
    throw '需要 PowerShell 2.0 或更高版本。'
}

# .NET TLS 1.2(对 BITS 无效,但对 Invoke-WebRequest 等有用)
try {
    [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072
} catch {
    Write-Host '注意: 无法设置 .NET TLS 1.2,BITS 下载走 WinHTTP 通道不受影响。'
}

# WinHTTP TLS 1.2 注册表配置(BITS 依赖此项)
function 启用-WinHttpTls12 {
    # 使用 reg.exe 而非 PS 注册表驱动,避免 PS 2.0 兼容性问题
    $路径列表 = @(
        'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp',
        'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp'
    )

    foreach ($路径 in $路径列表) {
        try {
            $regArgs = @('add', "HKLM\$路径", '/v', 'DefaultSecureProtocols', '/t', 'REG_DWORD', '/d', '2048', '/f')
            $result = & reg.exe $regArgs 2>&1
            if ($LASTEXITCODE -eq 0) {
                Write-Host "已配置 WinHTTP TLS 1.2: HKLM\$路径"
            } else {
                Write-Host "注意: 无法配置 WinHTTP TLS 1.2: HKLM\$路径 ($result)"
            }
        } catch {
            Write-Host "注意: reg.exe 调用失败: $_"
        }
    }
}

function 取-系统架构标识 {
    $原始架构 = if ($env:PROCESSOR_ARCHITEW6432) {
        $env:PROCESSOR_ARCHITEW6432
    } elseif ($env:PROCESSOR_ARCHITECTURE) {
        $env:PROCESSOR_ARCHITECTURE
    } else {
        $null
    }

    if ([string]::IsNullOrEmpty($原始架构) -or $原始架构.Trim().Length -eq 0) {
        throw '未能自动检测远程系统架构。'
    }

    switch ($原始架构.ToUpperInvariant()) {
        'AMD64' { return 'win32-x64' }
        'X64' { return 'win32-x64' }
        'ARM64' { return 'win32-arm64' }
        default { throw ('暂不支持的远程系统架构: {0}' -f $原始架构) }
    }
}

function 取-安装目录 {
    param(
        [string]$通道,
        [string]$版本提交号
    )

    $安装根目录 = if ($通道 -eq 'insider') {
        Join-Path $HOME '.vscode-server-insiders\bin'
    } else {
        Join-Path $HOME '.vscode-server\bin'
    }

    return Join-Path $安装根目录 $版本提交号
}

function 取-下载地址 {
    param(
        [string]$通道,
        [string]$版本提交号,
        [string]$架构
    )

    $文件名 = 'vscode-server-{0}.zip' -f $架构
    return 'https://vscode.download.prss.microsoft.com/dbazure/download/{0}/{1}/{2}' -f $通道, $版本提交号, $文件名
}

function 停止-占用安装目录的进程 {
    param(
        [string]$安装目录,
        [string]$版本提交号
    )

    # PS 2.0 兼容:使用 Get-WmiObject 代替 Get-CimInstance
    $进程列表 = Get-WmiObject Win32_Process -ErrorAction SilentlyContinue | Where-Object {
        ($_.ExecutablePath -and ($_.ExecutablePath -like ($安装目录 + '*'))) -or
        ($_.CommandLine -and ($_.CommandLine -like ('*' + $版本提交号 + '*')))
    }

    if ($进程列表) {
        foreach ($进程 in $进程列表) {
            try {
                Stop-Process -Id $进程.ProcessId -Force -ErrorAction SilentlyContinue
            } catch {
            }
        }
    }
}

function 展开-服务器压缩包 {
    param(
        [string]$压缩包路径,
        [string]$目标目录
    )

    # 尝试 .NET 4.5 ZipFile,失败则回退到 Shell.Application COM
    $解压成功 = $false
    try {
        Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue
        [System.IO.Compression.ZipFile]::ExtractToDirectory($压缩包路径, $目标目录)
        $解压成功 = $true
        Write-Host '使用 .NET ZipFile 解压完成。'
    } catch {
        Write-Host '.NET ZipFile 不可用,尝试 COM 解压...'
    }

    if (-not $解压成功) {
        New-Item -ItemType Directory -Force $目标目录 | Out-Null
        $shell = New-Object -ComObject Shell.Application
        $zip = $shell.NameSpace($压缩包路径)
        if ($null -eq $zip) {
            throw '无法打开压缩包。'
        }
        foreach ($item in $zip.Items()) {
            $shell.NameSpace($目标目录).CopyHere($item, 0x14)
        }
        # COM CopyHere 是异步的,等待完成
        $等待上限 = 120
        $已等待 = 0
        while ($已等待 -lt $等待上限) {
            $目标项 = Get-ChildItem $目标目录 -ErrorAction SilentlyContinue
            $压缩项 = @($zip.Items())
            if ($目标项.Count -ge $压缩项.Count) {
                break
            }
            Start-Sleep -Seconds 1
            $已等待++
        }
        Write-Host 'COM 解压完成。'
    }

    # 处理单层内目录
    $目录项列表 = @(Get-ChildItem -Force $目标目录)
    if ($目录项列表.Count -eq 1 -and $目录项列表[0].PSIsContainer) {
        $内层目录 = $目录项列表[0]
        Get-ChildItem -Force $内层目录.FullName | Move-Item -Destination $目标目录 -Force
        Remove-Item $内层目录.FullName -Recurse -Force
    }

    # 处理 vscode-server-* 包装目录
    $包装目录 = Get-ChildItem -Force $目标目录 | Where-Object { $_.PSIsContainer -and $_.Name -like 'vscode-server-*' } | Select-Object -First 1
    if ($包装目录) {
        foreach ($包装目录项 in (Get-ChildItem -Force $包装目录.FullName)) {
            $目标路径 = Join-Path $目标目录 $包装目录项.Name
            if (-not (Test-Path $目标路径)) {
                Move-Item $包装目录项.FullName -Destination $目标目录 -Force
            }
        }
        Remove-Item $包装目录.FullName -Recurse -Force
    }

    Remove-Item $压缩包路径 -Force -ErrorAction SilentlyContinue
}

# ===== 主流程 =====

Write-Host ('远程 PowerShell 版本: {0}' -f $PSVersionTable.PSVersion)

# 配置 WinHTTP TLS 1.2(BITS 下载 HTTPS 必需)
启用-WinHttpTls12
Write-Host 'WinHTTP 配置完成,开始安装流程。'

$提交号 = '__提交号__'
$发布通道 = '__发布通道__'
$系统架构 = 取-系统架构标识
$最终安装目录 = 取-安装目录 -通道 $发布通道 -版本提交号 $提交号
$压缩包路径 = Join-Path $最终安装目录 ('vscode-server-{0}.zip' -f ([guid]::NewGuid().ToString('N')))
$上传压缩包路径 = Join-Path $HOME 'vscode-server-upload-temp.zip'
$下载地址 = 取-下载地址 -通道 $发布通道 -版本提交号 $提交号 -架构 $系统架构

Write-Host ('准备安装 VS Code 远程服务,提交号: {0}' -f $提交号)
Write-Host ('发布通道: {0}' -f $发布通道)
Write-Host ('自动检测到的系统架构: {0}' -f $系统架构)
Write-Host ('自动检测到的安装目录: {0}' -f $最终安装目录)
Write-Host ('下载地址: {0}' -f $下载地址)

Write-Host '停止占用进程...'
停止-占用安装目录的进程 -安装目录 $最终安装目录 -版本提交号 $提交号
Write-Host '进程检查完成。'
Write-Host '步骤A: 检查已有目录...'
if (Test-Path $最终安装目录) {
    Write-Host '步骤B: 安装目录已存在,保留目录本身。'
}
Write-Host '步骤D: 创建安装目录...'
New-Item -ItemType Directory -Force $最终安装目录 | Out-Null
Write-Host '步骤D1: 清理旧压缩包...'
Get-ChildItem -Path $最终安装目录 -Filter 'vscode-server*.zip' -ErrorAction SilentlyContinue |
    ForEach-Object {
        Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
    }
Write-Host '步骤E: 检查已上传压缩包...'
if (-not (Test-Path $上传压缩包路径)) {
    throw ('未找到已上传的压缩包: {0}' -f $上传压缩包路径)
}
Write-Host '步骤F: 移动已上传压缩包到安装目录...'
try {
    Move-Item -Path $上传压缩包路径 -Destination $压缩包路径 -Force -ErrorAction Stop
} catch {
    Write-Host ('移动失败,改用复制+删除: {0}' -f $_.Exception.Message)
    Copy-Item -Path $上传压缩包路径 -Destination $压缩包路径 -Force -ErrorAction Stop
    Remove-Item -Path $上传压缩包路径 -Force -ErrorAction SilentlyContinue
}
Write-Host '步骤G: 压缩包已就位。'

if (-not (Test-Path $压缩包路径)) {
    throw ('下载完成后未找到压缩包: {0}' -f $压缩包路径)
}

Write-Host '下载完成,开始解压。'
展开-服务器压缩包 -压缩包路径 $压缩包路径 -目标目录 $最终安装目录

Write-Host '安装完成,当前目录内容如下。'
Get-ChildItem -Force $最终安装目录 | Select-Object Name, Length, Mode | Format-Table -AutoSize