我是怎么把 Armoury Crate 的自动更新拦下来的
最近我想把电脑上的 Armoury Crate 固定在当前可用状态,不再让它自动拉起更新、自动下载,或者悄悄把现有组件覆盖掉。官方并没有给我一个真正意义上的“关闭更新”开关,所以我最后走的是更偏系统层的处理方式。
这次我的目标并不是强行卸载整个软件,而是尽量保留现有功能,只把更新链路打断。实际做下来,这个思路是可行的。
我的需求
- 保留 Armoury Crate 现有可用功能
- – 阻止后台自动更新
- – 阻止点击更新后继续下载更新包
- – 尽量让改动可回退,而不是一上来就硬删文件
我观察到的更新链路
排查之后,我发现影响更新的不只是前台界面里的“检查更新”按钮,后台还有几类东西会一起参与:
- Armoury Crate 自己的服务
- – ROG Live 相关服务
- – ASUS Software Manager
- – 驱动仓库和程序目录中的更新可执行文件
也就是说,如果只是在软件设置里关掉自动更新,通常不够稳。前台看起来关了,后台服务和更新程序还是可能继续工作。
我最后采用的方案
我没有直接删除更新文件,而是用了一个更稳妥的做法:
- 停止并禁用更新相关服务
- 2. 把关键更新程序改名为
*.disabled - 3. 保留回滚空间,后面如果要恢复,只需要把文件名改回去
这么做的好处很直接:
- 风险比直接删除低
- – 更容易验证每一步有没有生效
- – 后续恢复成本小
中间踩到的坑
管理员权限并不总是够用
一开始我以为“用管理员 PowerShell 跑脚本”就够了,结果很快发现并不是所有路径都这么简单。
有两个问题比较典型:
- 有些服务用
Stop-Service停止时会失败 - –
DriverStore里的更新程序即使拿到了文件权限,重命名时仍然可能提示“访问被拒绝”
后来我才确认,第二个问题的关键点在于:
- Windows 重命名文件时,不只看文件本身权限
- – 还会看父目录是否允许修改
所以我后面补了两层处理:
- 先处理父目录权限
- – 再处理目标文件权限
这样重命名才真正成功。
不是所有失败都代表方案无效
这次还有一个很容易误判的地方。
我第一次执行时,脚本在停某个服务时就中断了。表面看像是“方案失败”,但实际上只是脚本容错不够。后面我把逻辑改成了分层尝试和继续执行,问题就解决了。
这个经验很重要:系统层操作里,脚本能不能继续跑完,和方案本身有没有效果,是两回事。
最终验证结果
完成处理之后,我重新打开了 Armoury Crate 做验证。
现象很明确:
- 界面里仍然会提示有更新
- – 但是点击更新以后,没有真正开始下载
- – 观察网络流量时,网卡几乎没有下载速度
这说明一个关键点:
前台提示不等于更新链路还活着。
对我来说,这已经达成目标了。软件可以继续用,但更新程序已经很难再真正落地。
我为什么不直接删软件
很多人遇到 Armoury Crate 的第一反应,是直接卸载或者彻底删除所有 ASUS 组件。这个做法当然更干脆,但不一定适合所有人。
我更想要的是:
- 保留还能用的控制功能
- – 去掉我不想要的更新行为
所以这次选择的是“最小破坏”的方案,而不是“全盘清空”。
这套方法还有什么边界
目前这套方案已经足够拦住常规更新下载,但我还是会保留几个判断:
- 如果 BIOS 里启用了相关自动安装功能,系统层封堵可能会被重新补回
- – 如果后续厂商改了服务名、路径或者更新机制,旧脚本可能需要重新适配
- – 如果还想再稳一点,可以继续加一层防火墙出站阻断
我的建议
如果你也打算拦 Armoury Crate 更新,我更建议这样做:
- 先确认相关服务和更新程序到底在哪里
- 2. 优先用“禁服务 + 改名”而不是直接删除
- 3. 每做完一步都做一次前台验证
- 4. 最后再考虑是否需要防火墙补刀
这样整个过程会更可控,也更容易排错。
最后
这次折腾下来,我的结论很简单:
Armoury Crate 的更新不是一个单点开关,而是一整条链路。真正有效的做法,不是只关界面选项,而是把后台服务和更新执行文件一起处理掉。
至少在我这边,做到这一步之后,更新提示还在,但下载已经起不来了。这正是我想要的结果。
符代码:
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Test-IsAdministrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
if (-not (Test-IsAdministrator)) {
throw "This script must be run from an elevated PowerShell session."
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$manifestPath = Join-Path $scriptDir "armoury_update_disable_manifest_$timestamp.json"
$services = @(
'ArmouryCrateService',
'ROG Live Service',
'ASUSSoftwareManager',
'asus',
'asusm'
)
$files = @(
'C:\Windows\System32\DriverStore\FileRepository\rogms.inf_amd64_7bcd8839c8e4f2a7\ArmouryLiveUpdate.exe',
'C:\Windows\System32\DriverStore\FileRepository\rogms.inf_amd64_7bcd8839c8e4f2a7\ArmouryUpdate.exe',
'C:\Program Files (x86)\ASUS\ArmouryDevice\dll\FilterDriver\x64\Win11\ROGMS\ArmouryUpdate.exe',
'C:\Program Files (x86)\ASUS\ArmouryDevice\dll\FilterDriver\x64\Win10\ROGMS\ArmouryUpdate.exe'
)
$report = [ordered]@{
executedAt = (Get-Date).ToString('s')
services = @()
files = @()
notes = @(
'ASUS scheduled tasks were reviewed separately. No task with an explicit update executable was found, so this script does not disable ASUS tasks by default.'
)
}
function Disable-ServiceForUpdate {
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
$svc = Get-CimInstance Win32_Service -Filter "Name='$Name'" -ErrorAction SilentlyContinue
if (-not $svc) {
return [ordered]@{
name = $Name
found = $false
state = $null
startModeBefore = $null
changed = $false
note = 'Service not found'
}
}
$notes = @()
$changed = $false
if ($svc.State -eq 'Running') {
try {
Stop-Service -Name $Name -Force -ErrorAction Stop
$notes += 'Stopped via Stop-Service'
$changed = $true
} catch {
$notes += "Stop-Service failed: $($_.Exception.Message)"
& sc.exe stop $Name | Out-Null
Start-Sleep -Seconds 3
$afterStop = Get-CimInstance Win32_Service -Filter "Name='$Name'"
if ($afterStop.State -eq 'Running' -and $afterStop.ProcessId -gt 0) {
& taskkill.exe /PID $afterStop.ProcessId /F | Out-Null
$notes += "Killed service process PID $($afterStop.ProcessId)"
$changed = $true
}
}
}
& sc.exe config $Name start= disabled | Out-Null
$updated = Get-CimInstance Win32_Service -Filter "Name='$Name'"
if ($updated.StartMode -eq 'Disabled') {
$changed = $true
}
return [ordered]@{
name = $Name
found = $true
state = $updated.State
startModeBefore = $svc.StartMode
startModeAfter = $updated.StartMode
changed = $changed
pathName = $svc.PathName
note = ($notes -join '; ')
}
}
function Rename-UpdateBinary {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not (Test-Path -LiteralPath $Path)) {
return [ordered]@{
originalPath = $Path
found = $false
changed = $false
note = 'File not found'
}
}
$disabledPath = "$Path.disabled"
if (Test-Path -LiteralPath $disabledPath) {
return [ordered]@{
originalPath = $Path
disabledPath = $disabledPath
found = $true
changed = $false
note = 'Disabled copy already exists'
}
}
$parentDir = Split-Path -Parent $Path
& takeown.exe /F $parentDir /A | Out-Null
& icacls.exe $parentDir /grant Administrators:F | Out-Null
& takeown.exe /F $Path /A | Out-Null
& icacls.exe $Path /grant Administrators:F | Out-Null
Rename-Item -LiteralPath $Path -NewName ([IO.Path]::GetFileName($disabledPath)) -ErrorAction Stop
return [ordered]@{
originalPath = $Path
disabledPath = $disabledPath
found = $true
changed = $true
note = 'File renamed to .disabled'
}
}
foreach ($serviceName in $services) {
$report.services += Disable-ServiceForUpdate -Name $serviceName
}
foreach ($filePath in $files) {
$report.files += Rename-UpdateBinary -Path $filePath
}
$report | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $manifestPath -Encoding UTF8
$report | ConvertTo-Json -Depth 5