#Requires -Version 5.1#Requires -RunAsAdministrator
param ([Parameter(Mandatory = true, HelpMessage = "Specify the action: 'Install' or 'Uninstall'.")]
[ValidateSet("Install", "Uninstall")]
[string]Action,
[Parameter(HelpMessage = "Product ID for Office installation (e.g., ProPlus2021Volume, Standard2024Volume). Required if Action is 'Install'.")]
[string]$ProductId,
[Parameter(HelpMessage = "Optional. Comma-separated list of App IDs to install (e.g., 'Word,Excel,PowerPoint'). If not specified, all apps for the ProductId are installed.")]
[string]$AppIds
)
Global settings
$PSDefaultParameterValues['*:Encoding'] = 'utf8'[Console]::OutputEncoding = [System.Text.Encoding]::UTF8Add-Type -AssemblyName System.Windows.Forms
--- Administrative Privileges Check ---
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {Write-Warning "此脚本需要管理员权限。正在尝试以管理员身份重新启动..."powershellArgs = "-NoProfile -ExecutionPolicy Bypass -File `"PSCommandPath" -Action "Action`""
if (ProductId) { powershellArgs += " -ProductId `"ProductId"" } if ($AppIds) { $powershellArgs += " -AppIds "$AppIds`"" }
Start-Process powershell -Verb RunAs -ArgumentList $powershellArgs
Exit
}
--- Variables ---
odtUrl = "https://officecdn.microsoft.com/pr/wsus/setup.exe"workDir = Join-Path -Path env:TEMP -ChildPath "KMS_CX" # Changed to a more generic nameodtPath = Join-Path -Path workDir -ChildPath "setup.exe" # ODT executableconfigPath = Join-Path -Path workDir -ChildPath "config.xml" # Configuration file for ODTlogPath = Join-Path -Path $workDir -ChildPath "OfficeTool-log.txt"
--- Helper Functions ---
function Show-MessageBox {param ([string]Text,
[string]Caption,[System.Windows.Forms.MessageBoxButtons]Buttons = [System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]Icon = [System.Windows.Forms.MessageBoxIcon]::Information)[System.Windows.Forms.MessageBox]::Show($Text, $Caption, $Buttons, $Icon) | Out-Null}
function Write-Log {param ([string]$Message)timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"timestamp - $Message" | Out-File -FilePath $logPath -Append -Encoding UTF8}
--- Main Script Logic ---
1. Create Working Directory
Write-Host "`n[+] 正在创建工作目录..." -ForegroundColor Cyanif (-not (Test-Path $workDir)) {New-Item -Path $workDir -ItemType Directory -Force | Out-Null}Write-Log "工作目录已创建/确认: $workDir"Write-Host "✅ 工作目录准备完成: $workDir"
2. Download Office Deployment Tool (ODT)
Write-Host "`n[+] 正在检查/下载 Office Deployment Tool..." -ForegroundColor Cyanif (-not (Test-Path $odtPath) -or (Get-Item $odtPath).Length -eq 0) {try {Write-Host " 正在下载 $odtUrl 到 $odtPath..."Invoke-WebRequest -Uri $odtUrl -OutFile $odtPath -ErrorAction Stop -UseBasicParsingWrite-Log "ODT 下载成功: $odtPath"Write-Host "✅ ODT 下载完成." -ForegroundColor Green}catch {Write-Error "❌ 下载 ODT (setup.exe) 失败: (.Exception.Message)"Write-Log "错误: 下载 ODT 失败 - (.Exception.Message)"Show-MessageBox "下载 Office Deployment Tool 失败。请检查网络连接或防火墙设置。" "下载失败" "OK" "Error"Exit 1}}else {Write-Log "ODT 已存在: $odtPath"Write-Host "✅ ODT 已存在,跳过下载." -ForegroundColor Green}
--- Action: Uninstall ---
if ($Action -eq "Uninstall") {Write-Host "`n[*] 选择的操作: 卸载 Office" -ForegroundColor MagentaWrite-Log "操作开始: 卸载 Office"
# 3. Generate Uninstall Configuration File
Write-Host "`n[1/3] 正在生成卸载配置文件..." -ForegroundColor Cyan
$uninstallConfigContent = @"
<Configuration ID="office-uninstall-script-(Get-Random)">
<Remove All="TRUE" />
<RemoveMSI />
<Display Level="None" AcceptEULA="TRUE" />
<Logging Level="Standard" Path="workDir" /></Configuration>"@try {$uninstallConfigContent | Out-File -Encoding UTF8 -FilePath $configPath -ForceWrite-Log "卸载配置文件创建成功: $configPath"Write-Host "✅ 卸载配置文件创建成功: $configPath"}catch {Write-Error "❌ 创建卸载配置文件失败: (.Exception.Message)"Write-Log "错误: 创建卸载配置文件失败 - (.Exception.Message)"Show-MessageBox "创建卸载配置文件失败。" "操作失败" "OK" "Error"Exit 1}
# 4. Execute Uninstall Command
Write-Host "`n[2/3] 正在开始卸载 Office (这可能需要一些时间)..." -ForegroundColor Yellow
Write-Log "执行卸载命令: $odtPath /configure `"$configPath`""
try {
$process = Start-Process -FilePath $odtPath -ArgumentList "/configure `"$configPath`"" -WorkingDirectory $workDir -Wait -PassThru -ErrorAction Stop
if ($process.ExitCode -ne 0) {
throw "卸载过程返回非零退出代码: $($process.ExitCode)"
}
Write-Log "Office 卸载操作已完成。退出代码: $($process.ExitCode)"
Write-Host "✅ Office 卸载操作已完成。" -ForegroundColor Green
}
catch {
Write-Error "❌ Office 卸载失败: $($_.Exception.Message)"
Write-Log "错误: Office 卸载执行失败 - $($_.Exception.Message)"
Show-MessageBox "Office 卸载过程中发生错误。请检查 $logPath 获取详细信息。" "卸载失败" "OK" "Error"
# Consider not exiting to allow cleanup and final message
}
# 5. Final Uninstall Message
Write-Host "`n[3/3] 全部卸载操作完成。" -ForegroundColor Green
Write-Log "建议重启计算机以清理残留文件。"
Show-MessageBox "Office 卸载已完成。建议重启计算机以清理残留文件。" "操作完成"
}
--- Action: Install ---
elseif ($Action -eq "Install") {Write-Host "`n[*] 选择的操作: 安装 Office" -ForegroundColor MagentaWrite-Log "操作开始: 安装 Office"
# Validate ProductId (can also use $env:OFF_PID as fallback from original script)
if (-not $ProductId) { $ProductId = $env:OFF_PID }
if (-not $ProductId) {
Write-Error "❌ 错误: 必须通过参数 -ProductId 或环境变量 OFF_PID 指定产品ID才能进行安装。"
Write-Log "错误: 安装时未指定 ProductId。"
Show-MessageBox "必须指定产品ID (例如 ProPlus2021Volume) 才能安装Office。" "参数错误" "OK" "Error"
Exit 1
}
# Use $env:OFF_AID as fallback for AppIds if provided
if (-not $AppIds -and $env:OFF_AID) { $AppIds = $env:OFF_AID }
Write-Host " 产品 ID (ProductId): $ProductId" -ForegroundColor White
Write-Log "ProductId: $ProductId"
if ($AppIds) {
Write-Host " 应用 ID (AppIds): $AppIds" -ForegroundColor White
Write-Log "AppIds: $AppIds"
}
# 3. Terminate existing Office processes (from original install script)
Get-Process -Name "OfficeC2RClient", "outlook", "winword", "excel", "powerpnt", "msaccess", "visio", "project", "onenote", "mspub", "lync", "teams", "setup", "odt" -ErrorAction SilentlyContinue | ForEach-Object {
Write-Host " 正在终止冲突进程: $($_.Name)..." -ForegroundColor Yellow
Write-Log "终止进程: $($_.Name) (ID: $($_.Id))"
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 300
}
# 4. Test API Connectivity and Construct Configuration URL
Write-Host "`n[1/4] 正在测试 API 连接并构造配置 URL..." -ForegroundColor Cyan
$apiUrls = @("https://k.302.pub/gen", "https://api.kms.cx/gen") # Original URLs
$selectedApiUrl = $null
foreach ($url in $apiUrls) {
try {
Write-Host " 正在尝试连接 $url ..."
Invoke-WebRequest -Uri $url -Method Head -TimeoutSec 7 -ErrorAction Stop -UseBasicParsing
Write-Host " ✅ 连接 $url 成功" -ForegroundColor Green
Write-Log "API 连接成功: $url"
$selectedApiUrl = $url
break
}
catch {
if ($_.Exception.Response -and $_.Exception.Response.StatusCode -eq 403) {
Write-Host " ⚠️ 连接 $url 返回 403 Forbidden,但视为可用。" -ForegroundColor Yellow
Write-Log "API 连接 $url 返回 403 (视为可用)"
$selectedApiUrl = $url
break
}
Write-Host " ❌ 连接 $url 失败: $($_.Exception.Message)" -ForegroundColor Red
Write-Log "API 连接失败: $url - $($_.Exception.Message)"
}
}
if (-not $selectedApiUrl) {
Write-Error "❌ 无法连接到任何配置 API 地址。"
Write-Log "错误: 无法连接到任何配置 API 地址。"
Show-MessageBox "无法连接到任何 Office 配置 API 地址。请检查网络。" "连接失败" "OK" "Error"
Exit 1
}
Write-Host " 使用 API URL: $selectedApiUrl" -ForegroundColor Cyan
# 使用 -f 操作符构建基础 URL (不进行客户端编码)
$dynamicConfigUrl = "{0}?p={1}" -f $selectedApiUrl, $ProductId
if ($AppIds) {
$trimmedAppIdsArray = ($AppIds.Split(',') | ForEach-Object { $_.Trim() })
$appIdsString = $trimmedAppIdsArray -join ','
$dynamicConfigUrl = "{0}&a={1}" -f $dynamicConfigUrl, $appIdsString
}
Write-Host " 生成的配置 URL: $dynamicConfigUrl" -ForegroundColor Yellow
Write-Log "生成的配置 URL: $dynamicConfigUrl"
# 5. Execute Install Command
Write-Host "`n[2/4] 正在开始安装 Office (这可能需要较长时间,请耐心等待)..." -ForegroundColor Yellow
Write-Log "执行安装命令: $odtPath /configure `"$dynamicConfigUrl`""
try {
# Using Start-Process to better manage output and errors like in original install script
$processInfo = New-Object System.Diagnostics.ProcessStartInfo
$processInfo.FileName = $odtPath
$processInfo.Arguments = "/configure `"$dynamicConfigUrl`"" # Enclose URL in quotes
$processInfo.WorkingDirectory = $workDir
$processInfo.UseShellExecute = $false
$processInfo.RedirectStandardOutput = $true
$processInfo.RedirectStandardError = $true
$processInfo.CreateNoWindow = $true # Try to hide ODT window if possible
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $processInfo
$process.Start() | Out-Null
$stdOutput = $process.StandardOutput.ReadToEndAsync()
$stdError = $process.StandardError.ReadToEndAsync()
$process.WaitForExit() # Wait indefinitely
$exitCode = $process.ExitCode
$outputLog = $stdOutput.Result
$errorLog = $stdError.Result
if ($outputLog) { Write-Log "ODT stdout: $outputLog" }
if ($errorLog) { Write-Log "ODT stderr: $errorLog" }
if ($exitCode -ne 0) {
throw "Office 安装失败。退出代码: $exitCode。错误流: $errorLog"
}
Write-Log "Office 安装成功。退出代码: $exitCode"
Write-Host "✅ Office 安装成功。" -ForegroundColor Green
if ($outputLog) { Write-Host "安装日志摘要:`n$outputLog" -ForegroundColor Gray }
}
catch {
Write-Error "❌ Office 安装失败: $($_.Exception.Message)"
Write-Log "错误: Office 安装执行失败 - $($_.Exception.Message)"
if ($errorLog) { Write-Error "详细错误: $errorLog" }
Show-MessageBox "Office 安装过程中发生错误。请检查 $logPath 获取详细信息。`n$($_.Exception.Message)" "安装失败" "OK" "Error"
# Consider not exiting to allow cleanup
}
# 6. Create Desktop Shortcuts (from original install script)
Write-Host "`n[3/4] 正在创建桌面快捷方式..." -ForegroundColor Cyan
function New-DesktopShortcut {
param (
[string]$Target,
[string]$Name
)
$shortcutPath = Join-Path -Path $env:USERPROFILE -ChildPath "Desktop\$Name.lnk"
if (-not (Test-Path $shortcutPath)) {
try {
$shell = New-Object -ComObject WScript.Shell
$shortcut = $shell.CreateShortcut($shortcutPath)
$shortcut.TargetPath = $Target
# You can also set IconLocation, WorkingDirectory etc. if needed
# $shortcut.IconLocation = "$Target,0"
# $shortcut.WorkingDirectory = Split-Path $Target -Parent
$shortcut.Save()
Write-Host " ✅ 已创建快捷方式: $Name.lnk" -ForegroundColor Green
Write-Log "创建桌面快捷方式: $Name.lnk -> $Target"
}
catch {
Write-Warning " ⚠️ 创建快捷方式 $Name.lnk 失败: $($_.Exception.Message)"
Write-Log "警告: 创建快捷方式 $Name.lnk 失败 - $($_.Exception.Message)"
}
}
else {
Write-Host " ℹ️ 快捷方式已存在: $Name.lnk" -ForegroundColor White
Write-Log "桌面快捷方式已存在: $Name.lnk"
}
}
# Common Office installation paths
# $programFiles = $env:ProgramFiles
# $programFilesX86 = $env:ProgramFilesX86
# # Potential Office paths, Office16 is common for modern versions (2016, 2019, 2021, 365)
# $officePaths = @(
# Join-Path -Path $programFiles -ChildPath "Microsoft Office\root\Office16",
# Join-Path -Path $programFiles -ChildPath "Microsoft Office\Office16"
# )
$foundOfficeApps = @{} # To avoid duplicate shortcuts if apps are in multiple lnk files
# Search in Start Menu Programs for Office .lnk files
$startMenuProgramsPath = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs"
if (Test-Path $startMenuProgramsPath) {
Get-ChildItem -Path $startMenuProgramsPath -Filter *.lnk -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
try {
$shell = New-Object -ComObject WScript.Shell
$shortcut = $shell.CreateShortcut($_.FullName)
$targetPath = $shortcut.TargetPath
# Check if the target is an Office executable in a typical Office path
if ($targetPath -and ($targetPath -match "\\Office\d{2}\\[^\\]+\.exe$" -or $targetPath -match "root\\Office\d{2}\\[^\\]+\.exe$")) {
$appName = [System.IO.Path]::GetFileNameWithoutExtension($_.Name)
$exeName = [System.IO.Path]::GetFileName($targetPath)
# Filter common Office apps
if ("WINWORD.EXE", "EXCEL.EXE", "POWERPNT.EXE", "OUTLOOK.EXE", "MSACCESS.EXE", "MSPUB.EXE", "ONENOTE.EXE", "VISIO.EXE", "PROJECT.EXE" -contains $exeName.ToUpperInvariant()) {
if (-not $foundOfficeApps.ContainsKey($appName)) {
New-DesktopShortcut -Target $targetPath -Name $appName
$foundOfficeApps[$appName] = $true
}
}
}
}
catch {
Write-Warning " ⚠️ 处理开始菜单快捷方式 '$($_.Name)' 时出错: $($_.Exception.Message)"
Write-Log "警告: 处理开始菜单快捷方式 '$($_.Name)' 时出错 - $($_.Exception.Message)"
}
}
}else {
Write-Warning " ⚠️ 开始菜单程序文件夹未找到: $startMenuProgramsPath"
Write-Log "警告: 开始菜单程序文件夹未找到: $startMenuProgramsPath"
}
if ($foundOfficeApps.Count -eq 0) {
Write-Warning " ⚠️ 未能在开始菜单中找到Office应用程序快捷方式来复制到桌面。"
Write-Log "警告: 未能在开始菜单中找到Office应用程序快捷方式来复制到桌面。"
}
# 7. Final Install Message
Write-Host "`n[4/4] Office 安装操作完成。" -ForegroundColor Green
}else {Write-Error "无效的操作 'Action'。请使用 'Install' 或 'Uninstall'。"
Write-Log "错误: 无效的操作参数 'Action'"Exit 1}
--- Cleanup (Optional: remove $odtPath if no longer needed, $workDir could be kept for logs) ---
If you want to remove the downloaded setup.exe after use:
if (Test-Path $odtPath) {
Write-Host "`n[+] 正在清理下载的 ODT..." -ForegroundColor Cyan
Remove-Item -Path $odtPath -Force -ErrorAction SilentlyContinue
Write-Log "已清理 ODT: $odtPath"
}
Write-Host "`n✅ 脚本执行完毕。" -ForegroundColor GreenWrite-Log "脚本执行完毕。"Exit 0
网上找的脚本,运行参数示例:& ~/XXX.ps1 -Action Install -productId ProPlus2021Volume -appIds 'Excel,PowerPoint,Word'