Docker Desktop / WSL 启动失败排查全集:0x80070570、1603、START_PENDING、AppX 卡死一文搞定

文章来源声明: 原文作者:用户169440981519; 来源站点:掘金; 原文链接:https://juejin.cn/post/7692977150127816767; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

把三个月血泪排障浓缩成症状速查表,配 SQLite 损坏检测、VSS 提取等开源脚本,适合被 WSL 与 Docker 反复折磨的 Windows 开发者收藏备用。

> 适用症状:Docker Desktop 引擎起不来、`wsl --import` 报错或挂起、WSL 升级 MSI 失败、`wslservice.exe` 卡 START\_PENDING、Chrome 数据库异常清空。环境:Windows 11 + Docker Desktop 4.83 + WSL 3.0.1。全部方案均为本人实测(2026-10,3 个月排障实录),按症状索引,直接对号入座。

症状速查表

症状真实原因跳转
`Wsl/.../HCS/0x80070570` 导入发行版失败vhd/文件系统损坏[§1](#1-0x80070570%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%8D%9F%E5%9D%8F "#1-0x80070570%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%8D%9F%E5%9D%8F")
`WslService` 卡 START\_PENDING,CPU=0Lxss 注册表幽灵发行版键[§2](#2-wslservice-%E5%8D%A1-start_pending "#2-wslservice-%E5%8D%A1-start_pending")
WSL MSI 安装卡死在最后一步DeprovisionMsix + AppX 栈僵死[§3](#3-msi-%E5%8D%A1%E6%AD%BB-deprovisionmsix "#3-msi-%E5%8D%A1%E6%AD%BB-deprovisionmsix")
MSI 报 1603/1619布局问题/事务残留[§4](#4-msi-1603--1619 "#4-msi-1603--1619")
浏览器登录态反复丢失、SQLite 库表为空文件系统层损坏[§5](#5-chrome-sqlite-%E5%BA%93%E5%85%A8%E7%A9%BA%E4%B8%8E-cookie-%E8%BF%81%E7%A7%BB "#5-chrome-sqlite-%E5%BA%93%E5%85%A8%E7%A9%BA%E4%B8%8E-cookie-%E8%BF%81%E7%A7%BB")
引擎修好了但 pull 不动镜像直连 registry 被墙[§6](#6-%E9%95%9C%E5%83%8F%E6%8B%89%E5%8F%96%E5%8A%A0%E9%80%9F "#6-%E9%95%9C%E5%83%8F%E6%8B%89%E5%8F%96%E5%8A%A0%E9%80%9F")

  1. 0x80070570(文件系统损坏)

现象:Docker 后端日志出现:

wsl.exe --import-in-place docker-desktop ... failed:
文件或目录损坏且无法读取。
错误代码: Wsl/Service/RegisterDistro/CreateVm/HCS/0x80070570

定位:0x80070570 = ERROR_FILE_CORRUPT。vhd/vhdx 或其所在卷的文件系统损坏。

修复:

:: 1. 管理员 PowerShell,预约开机自检(C 盘无法在线锁卷)
chkdsk C: /f
:: 按提示输入 Y,重启。观察关键字段:
::   "0 KB in bad sectors" → 硬件无损,纯文件系统层损坏(可放心)
::   有坏簇数字 → 立即备份数据,考虑换盘
​
:: 2. 用安装包内的干净模板替换损坏的 vhdx
copy /Y "C:\Program Files\Docker\Docker\resources\wsl\ext4.vhdx" ^
    "%LOCALAPPDATA%\Docker\wsl\main\ext4.vhdx"
​
:: 3. SMART 佐证(硬件健康与否)
powershell "Get-PhysicalDisk | Format-Table FriendlyName,HealthStatus"

注意:chkdsk 通过 ≠ 万事大吉。文件系统损坏往往是连环事故的第一张多米诺,后续 WSL/浏览器异常接着查。


  1. WslService 卡 START_PENDING

现象:sc query WslService 显示 STATE : 2 START_PENDING 数小时不变;服务进程 CPU=0;wsl.exe 任何命令挂起 45s+。

排查顺序(本人实测有效的优先级):


# ① 依赖服务
Get-Service vmcompute, hns          # 必须 Running
​
# ② 插件目录
dir "C:\Program Files\WSL\lib"     # 只有 3 个 GPU dll 是正常的
​
# ③ ★幽灵发行版注册键(本例真凶)★
reg query "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss" /s
# 逐个核对每个 GUID 键的 BasePath + VhdFileName 指向的文件是否存在

修复:删掉指向不存在文件的发行版键(服务卡死时 wsl --unregister 无效,只能删注册表):


reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss{GUID}" /f
taskkill /F /IM wslservice.exe
sc start WslService

效果:wsl --version 从 45s 超时 → 131ms。

原理:WSL 是"文件 + 注册表"双状态组件。vhd 文件删了但注册表键残留,服务枚举发行版时撞上幽灵键即挂死。凡是手动删过 vhd / 重装过 Docker 的机器都要查这一步。


  1. MSI 卡死(DeprovisionMsix)

现象:wsl --update 或手动运行 WSL 的 msi,进度条卡在最后一步,verbose log 尾部永远是:


ActionStart(Name=DeprovisionMsix,,)
CustomActionSchedule(Action=DeprovisionMsix,ActionType=3073,...)

原因:该自定义动作要反预配(deprovision)收件箱版 WSL 的 MSIX 包,但机器的 AppX 部署栈僵死——验证方法(会挂起即中招):


Get-AppxPackage -AllUsers -Name "*WindowsSubsystem*"   # 挂起 = AppX 栈坏

修复(三层递进) :


:: 第 1 层:DISM 移除预配包(走独立部署路径,绕开僵死的 AppXSvc)
dism /online /Get-ProvisionedAppxPackages | findstr /i "Subsystem"
dism /online /Remove-ProvisionedAppxPackage ^
  /PackageName:MicrosoftCorporationII.WindowsSubsystemForLinux_<版本>_x64__8wekyb3d8bbwe
​
:: 第 2 层:每用户注册的包用 DISM 删不掉 → 干净启动
:: msconfig → 服务 → 勾选"隐藏所有 Microsoft 服务" → 全部禁用 → 重启
​
:: 第 3 层:干净启动下重跑 MSI,一次通过
msiexec /i "%TEMP%\wsl.3.0.1.0.x64.msi" /l*v "%TEMP%\wsl_install.log"

警告:干净启动会禁用全部第三方服务。已知副作用:浏览器登录态异常(见 §5)。修完后 msconfig → 常规 → 正常启动 → 再重启恢复。


  1. MSI 1603 / 1619

1603 布局陷阱(从管理解包/缓存目录拿 MSI 装时必踩):


MSI (s): Source for file 'RdpWinStlHelper.dll' is uncompressed, at '...\PFiles64\WSL'
MSI (s): Folder is not accessible: C:\Users...\Downloads\PFiles64

此类 MSI 的文件不内嵌 CAB,MSI 必须与 PFiles64\WSL 文件夹同级才能安装。单独复制 MSI 到任意目录运行必然 1603。

1619 = 安装包路径无效:卸载过程会删除 C:\Windows\Installer 下的缓存 MSI。卸载+重装分两步跑时,第二步会找不到包。对策:先备份缓存 MSI,或从官方 release 重下。

通用诊断命令(GUI 永远只说 1603,真相在日志里):


msiexec /i xxx.msi /l*v full.log
:: 在日志里搜(按优先级):
::   "return value 3"  → 第一个失败点,看它前 30 行
::   "Note: 1: 1714/1723/1603"
::   "DEBUG: Error"

挂起的事务清理:强杀 msiexec 后安装器报"当前安装处于挂起状态 (0x80070644)":


reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\InProgress"
:: 有值则删,或重启(SCM 会回收)


  1. Chrome SQLite 库全空与 Cookie 迁移

现象:所有网站登录态丢失;重新登录后重启浏览器即掉。特征性诊断结果:

检查项正常值受损值
文件头`SQLite format 3``SQLite format 3`(照样合法!)
sqlite\_master 表数量十几个0
非零字节占比>60%~9%

文件头合法 + 表结构清零 → Chrome 打开库认为正常,但写入全部落空 → 登录仅存内存 → 关浏览器即蒸发。这就是"怎么登都掉"的机理。

恢复决策树:


① 卷影副本恢复?
   vssadmin list shadows(管理员)
   copy "\?\GLOBALROOT\Device\HarddiskVolumeShadowCopyN\Users<user>...\Cookies" dst
   → 注意验证快照版本的表结构,文件系统损坏往往在快照前已发生
② 其他 Chromium 浏览器还有登录态?
   → Cookie 迁移(下述,本人验证可行的完整路径)
③ 都没有 → 只能账号找回

Cookie 迁移(Firefox → Chrome) :


# Step 1: 提取(Firefox 运行中库被锁,复制 + wal 即可读)
Copy-Item "$env:APPDATA\Mozilla\Firefox\Profiles<profile>\cookies.sqlite" D:\ff.db
python -c "import sqlite3; con=sqlite3.connect(r'D:\ff.db'); print(con.execute(
  "SELECT name,value FROM moz_cookies WHERE host LIKE '%bilibili%'").fetchall())"
​
# Step 2: Chrome 新版有 App-Bound 加密(Local State 密钥前缀 DPA),
#         直接写 Cookies 数据库的明文列会在启动时被清空 → 必须走 CDP 注入


# Step 3: CDP 注入(完整可运行,Python + websockets)
import subprocess, time, json, urllib.request, asyncio, websockets
​
CHROME = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
# 关键:默认 User Data 目录禁止开调试端口,必须复制一份副本配置
UD = r"C:\Temp\chrome_cdp"
COOKIES = json.load(open("bili_cookies.json"))   # 含 domain/name/value/path/secure/httpOnly/expires
​
subprocess.Popen([CHROME, "--remote-debugging-port=9222", f"--user-data-dir={UD}",
                  "--no-first-run", "about:blank"])
for _ in range(15):
    time.sleep(2)
    try:
        targets = json.loads(urllib.request.urlopen(
            "http://127.0.0.1:9222/json/list", timeout=2).read()); break
    except Exception: pass
​
page = next(t for t in targets if t["type"] == "page")
​
async def main():
    async with websockets.connect(page["webSocketDebuggerUrl"], max_size=10**7) as ws:
        mid = 0
        async def cmd(m, p):
            nonlocal mid; mid += 1
            await ws.send(json.dumps({"id": mid, "method": m, "params": p}))
            while True:
                r = json.loads(await ws.recv())
                if r.get("id") == mid: return r
        await cmd("Network.enable", {})
        for c in COOKIES:
            p = {"name": c["name"], "value": c["value"], "domain": c["domain"],
                 "path": c["path"], "secure": c["secure"], "httpOnly": c["httpOnly"]}
            if c.get("expires"): p["expires"] = c["expires"] / 1e6   # Chrome us → CDP s
            await cmd("Network.setCookie", p)
        # 验证(真实业务接口,落盘前最后防线)
        await cmd("Page.navigate", {"url": "https://www.bilibili.com"})
        ev = await cmd("Runtime.evaluate", {"expression":
            "fetch('https://api.bilibili.com/x/web-interface/nav',{credentials:'include'})"
            ".then(r=>r.json()).then(j=>j.data.isLogin?'LOGGED_IN':'FAIL')",
            "awaitPromise": True, "returnByValue": True})
        print(ev["result"]["result"]["value"])
​
asyncio.run(main())


# Step 4: ★优雅关闭(绝不能 taskkill /F!强杀不触发 Cookie 落盘)
(Get-Process chrome | ForEach-Object { $_.CloseMainWindow() }) ; Start-Sleep 6
​
# Step 5: 把临时配置的登录态拷回真实配置(Chrome 必须已完全退出)
Copy-Item "C:\Temp\chrome_cdp\Default\Network\Cookies" `
          "$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Network\Cookies" -Force
Copy-Item "C:\Temp\chrome_cdp\Local State" `
          "$env:LOCALAPPDATA\Google\Chrome\User Data\Local State" -Force

验证:重启 Chrome,F12 → Application → Cookies 确认 SESSDATA 存在;关开浏览器不掉即成功。


  1. 镜像拉取加速

引擎就绪后 docker pull 超时的最后一块拼图——直连 registry 被墙。编辑 %USERPROFILE%.docker\daemon.json:


{
  "builder": { "gc": { "defaultKeepStorage": "20GB", "enabled": true } },
  "experimental": false,
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io",
    "https://dockerproxy.net"
  ]
}

重启 Docker Desktop 后验证:


docker info | findstr -A 4 "Registry"
docker pull hello-world   → Status: Downloaded newer image


附:完整修复时序(可直接照抄的操作顺序)


1. chkdsk C: /f → 重启自检 → 确认 0 bad sectors
2. reg query HKCU...\Lxss /s → 删除指向不存在文件的发行版键
3. wsl --unregister docker-desktop && 用安装包模板重新导入
4. wsl -d <distro> echo BOOT_OK   ← 209ms 内返回才算服务健康
5. 启动 Docker Desktop → docker info 看到 Server 段即成功
6. daemon.json 加 registry-mirrors → docker pull 验证

血泪经验(全文浓缩)

  1. GUI 错误码一文不值,verbose log 才是真相(MSI /l*v、Docker com.docker.backend.exe.log)
  2. WSL/MSI 这类"文件+注册表"双状态组件,挂死先查注册表残留
  3. 落盘判成功:CDP success ≠ 数据在磁盘;taskkill /F 是 Cookie 消失的头号元凶
  4. 文件系统损坏是连环事故:vhd、SQLite 库会接连中招,修复后必须全盘体检
  5. 干净启动是大杀器也是双刃剑:修好了部署栈,丢了浏览器登录态——用前关浏览器,用后备好账号密码

本文所有命令在 Windows 11 24H2 + Docker Desktop 4.83 + WSL 3.0.1 实测通过。转载请注明出处。

本文全部脚本已开源:github.com/TrueFurina/… (sqlite 损坏检测 / VSS 提取 / Firefox Cookie 提取 / CDP 注入 / 优雅落盘,觉得有用点个 Star ⭐)


附录 A:SQLite 结构性损坏检测脚本

§5 场景的快速定性工具——判断任意 SQLite 文件是"真损坏"还是"表结构清零"(两者处置完全不同):


# sqlite_check.py
import sqlite3, sys
​
def check(path):
    with open(path, "rb") as f:
        head = f.read(65536)
    ok_header = head[:16] == b'SQLite format 3\x00'
    nonzero = sum(1 for b in head if b) / len(head)
    print(f"文件头: {head[:16]}  合法={ok_header}")
    print(f"前64K非零字节占比: {nonzero:.1%}   (<30% 高度可疑)")
    try:
        con = sqlite3.connect(path)
        tables = [r[0] for r in con.execute(
            "SELECT name FROM sqlite_master WHERE type='table'")]
        print(f"表: {tables}")
    except Exception as e:
        print(f"打开失败: {e}")
        return False
    return ok_header and len(tables) > 0
​
for p in sys.argv[1:]:
    print(f"\n=== {p} ===")
    print("健康" if check(p) else "★结构性损坏(表清零)★")

判读矩阵:

文件头表数量结论处置
合法>0健康正常使用
合法0**结构性损坏**(本案例)移除重建,数据无法自恢复
非法-文件级损坏换文件/恢复备份

Chrome 场景下批量检测:


python sqlite_check.py ^
  "%LOCALAPPDATA%\Google\Chrome\User Data\Default\Network\Cookies" ^
  "%LOCALAPPDATA%\Google\Chrome\User Data\Default\History" ^
  "%LOCALAPPDATA%\Google\Chrome\User Data\Default\Login Data" ^
  "%LOCALAPPDATA%\Google\Chrome\User Data\Default\Web Data"

附录 B:卷影副本(VSS)提取与验证

§5 恢复决策树第一分支的完整操作。前提:系统保护已开启(很多机器默认只对系统盘开)。


:: 1. 列出快照(管理员)
vssadmin list shadows
:: 记录 Shadow Copy Volume: \?\GLOBALROOT\Device\HarddiskVolumeShadowCopy<N>
:: 注意 "Creation Time" —— 只有早于损坏时间的快照才有意义
​
:: 2. cmd 提取会因路径转义失败(\?\GLOBALROOT 反斜杠被吃),必须 PowerShell:


# vss_extract.ps1(管理员运行)
$src = "\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Users<用户名>\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies"
New-Item -ItemType Directory -Force -Path "D:\recover" | Out-Null
try {
    Copy-Item -LiteralPath $src -Destination "D:\recover\Cookies_snapshot" -Force -EA Stop
    "COPY_OK"
} catch { "COPY_FAIL: $($_.Exception.Message)" }


:: 3. ★必做:验证快照版本健康度(用附录 A 脚本)
python sqlite_check.py "D:\recover\Cookies_snapshot"
:: 表为空 = 快照时已损坏 → 此路不通,转 §5 决策树下一分支

实测教训:文件系统损坏是渐进的。本案例 10/5 中午的快照提取成功,但快照里的库同样是空表——白高兴一场。快照越早越好。


# ff_extract.py — Firefox 多容器会产生同名 Cookie 副本,必须去重
import sqlite3, json, shutil
​
# Firefox 运行中库被锁,先复制(连同 WAL,否则可能只读到旧数据)
PROF = r"C:\Users<用户名>\AppData\Roaming\Mozilla\Firefox\Profiles<profile>"
shutil.copyfile(PROF + r"\cookies.sqlite", r"D:\ff.db")
​
con = sqlite3.connect(r"D:\ff.db")
con.row_factory = sqlite3.Row
rows = con.execute("SELECT * FROM moz_cookies WHERE host LIKE '%目标域%'").fetchall()
​
seen = {}   # (host, name, path) 三元组去重
for r in rows:
    seen[(r['host'], r['name'], r['path'])] = dict(r)
​
out = [{
    "domain": r['host'], "name": r['name'], "value": r['value'],
    "path": r['path'],
    "expires": r['expiry']*1000000 + 11644473600000000,  # Unix秒 → Chrome微秒
    "secure": bool(r['isSecure']), "httpOnly": bool(r['isHttpOnly']),
} for r in seen.values()]
​
json.dump(out, open("cookies.json", "w"), ensure_ascii=False)
print(f"导出 {len(out)} 条")

前置校验(别急着迁移,先确认凭证还有效):


import sqlite3, time
con = sqlite3.connect(r"D:\ff.db")
now = time.time()
for n, h, v, e in con.execute(
    "SELECT name,host,value,expiry FROM moz_cookies "
    "WHERE name IN ('SESSDATA','DedeUserID','bili_jct')"):
    print(f"{h} {n}: len={len(v)} {'有效' if e > now else '★已过期'}")

附录 D:AppX 栈僵死自检三连

§3 的快速定性:


# 1. 查询挂起(>60s 无响应即中招)
Measure-Command { Get-AppxPackage -AllUsers -Name "*Subsystem*" } 
​
# 2. 预配包列表(DISM 独立路径,AppXSvc 僵死时依然可用)
dism /online /Get-ProvisionedAppxPackages | findstr /i "Subsystem"
​
# 3. AppX 服务状态
Get-Service AppXSvc, ClipSVC, StateRepository | Format-Table Name, Status

三个结果对应三种处置:

  • ① 挂起 + ③ Running → AppX 栈假死,干净启动最稳
  • ② 有输出 → 用 DISM 删预配包(§3 第 1 层)
  • ② 也挂 → 只剩干净启动一条路(§3 第 2 层)

附录 E:Docker 引擎健康自检 30 秒清单


# ① 三个依赖服务全 RUNNING?
Get-Service WslService, vmcompute, hns | Format-Table Name, Status
​
# ② 发行版列表秒回?(>30s = 回去查 §2 幽灵键)
wsl -l -v
​
# ③ 发行版能启动?(秒回 BOOT_OK 才算健康)
wsl -d docker-desktop sh -c "echo BOOT_OK"
​
# ④ 后端日志最新错误(GUI 只会说 unable to start)
Get-Content "$env:LOCALAPPDATA\Docker\log\host\com.docker.backend.exe.log" -Tail 100 |
    Select-String "TimedOut|failed to start|0x8"
​
# ⑤ 引擎探活(有 Server 版本号 = 活着)
docker info --format "{{.ServerVersion}} {{.Driver}}"

附录 F:本次修复全程操作时序(可照抄)


第 1 天:定性
  1. chkdsk C: /f(预约)→ 重启自检 → 确认 0 bad sectors
  2. Get-PhysicalDisk → SMART Healthy → 确认硬件无损
第 2 天:重建
  3. reg query HKCU...\Lxss /s → 删幽灵发行版键(§2)
  4. wsl --unregister docker-desktop → 模板重导入
  5. wsl -d docker-desktop echo BOOT_OK(209ms = 服务健康)
  6. Docker Desktop → docker info 见 Server 段
  7. daemon.json 加 registry-mirrors → docker pull hello-world 成功(§6)
插曲(同步进行):
  A. Chrome 六库全空 → VSS 提取失败(快照已坏)→ Firefox Cookie 迁移(§5)
  B. WSL 2.7→3.0.1 MSI 五连败 → DISM 删预配包 + 干净启动 → 装好(§3/§4)

附录 A–E 脚本均为实测原样,参数中的 <用户名>/<profile>/<GUID> 按机器替换。