我是怎么把 Armoury Crate 的自动更新拦下来的

我是怎么把 Armoury Crate 的自动更新拦下来的

最近我想把电脑上的 Armoury Crate 固定在当前可用状态,不再让它自动拉起更新、自动下载,或者悄悄把现有组件覆盖掉。官方并没有给我一个真正意义上的“关闭更新”开关,所以我最后走的是更偏系统层的处理方式。

这次我的目标并不是强行卸载整个软件,而是尽量保留现有功能,只把更新链路打断。实际做下来,这个思路是可行的。

我的需求

  • 保留 Armoury Crate 现有可用功能
  • – 阻止后台自动更新
  • – 阻止点击更新后继续下载更新包
  • – 尽量让改动可回退,而不是一上来就硬删文件

我观察到的更新链路

排查之后,我发现影响更新的不只是前台界面里的“检查更新”按钮,后台还有几类东西会一起参与:

  • Armoury Crate 自己的服务
  • – ROG Live 相关服务
  • – ASUS Software Manager
  • – 驱动仓库和程序目录中的更新可执行文件

也就是说,如果只是在软件设置里关掉自动更新,通常不够稳。前台看起来关了,后台服务和更新程序还是可能继续工作。

我最后采用的方案

我没有直接删除更新文件,而是用了一个更稳妥的做法:

  1. 停止并禁用更新相关服务
  2. 2. 把关键更新程序改名为 *.disabled
  3. 3. 保留回滚空间,后面如果要恢复,只需要把文件名改回去

这么做的好处很直接:

  • 风险比直接删除低
  • – 更容易验证每一步有没有生效
  • – 后续恢复成本小

中间踩到的坑

管理员权限并不总是够用

一开始我以为“用管理员 PowerShell 跑脚本”就够了,结果很快发现并不是所有路径都这么简单。

有两个问题比较典型:

  • 有些服务用 Stop-Service 停止时会失败
  • DriverStore 里的更新程序即使拿到了文件权限,重命名时仍然可能提示“访问被拒绝”

后来我才确认,第二个问题的关键点在于:

  • Windows 重命名文件时,不只看文件本身权限
  • – 还会看父目录是否允许修改

所以我后面补了两层处理:

  • 先处理父目录权限
  • – 再处理目标文件权限

这样重命名才真正成功。

不是所有失败都代表方案无效

这次还有一个很容易误判的地方。

我第一次执行时,脚本在停某个服务时就中断了。表面看像是“方案失败”,但实际上只是脚本容错不够。后面我把逻辑改成了分层尝试和继续执行,问题就解决了。

这个经验很重要:系统层操作里,脚本能不能继续跑完,和方案本身有没有效果,是两回事。

最终验证结果

完成处理之后,我重新打开了 Armoury Crate 做验证。

现象很明确:

  • 界面里仍然会提示有更新
  • – 但是点击更新以后,没有真正开始下载
  • – 观察网络流量时,网卡几乎没有下载速度

这说明一个关键点:

前台提示不等于更新链路还活着。

对我来说,这已经达成目标了。软件可以继续用,但更新程序已经很难再真正落地。

我为什么不直接删软件

很多人遇到 Armoury Crate 的第一反应,是直接卸载或者彻底删除所有 ASUS 组件。这个做法当然更干脆,但不一定适合所有人。

我更想要的是:

  • 保留还能用的控制功能
  • – 去掉我不想要的更新行为

所以这次选择的是“最小破坏”的方案,而不是“全盘清空”。

这套方法还有什么边界

目前这套方案已经足够拦住常规更新下载,但我还是会保留几个判断:

  • 如果 BIOS 里启用了相关自动安装功能,系统层封堵可能会被重新补回
  • – 如果后续厂商改了服务名、路径或者更新机制,旧脚本可能需要重新适配
  • – 如果还想再稳一点,可以继续加一层防火墙出站阻断

我的建议

如果你也打算拦 Armoury Crate 更新,我更建议这样做:

  1. 先确认相关服务和更新程序到底在哪里
  2. 2. 优先用“禁服务 + 改名”而不是直接删除
  3. 3. 每做完一步都做一次前台验证
  4. 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