WorkBuddy 数据目录迁移(C 盘 → 其他盘)
用途
将 WorkBuddy 的数据目录 C:\Users\<用户名>\.workbuddy 整体迁移到另一块盘(默认 D:\WorkBuddy),用目录联接(junction)替代原目录。软件无感,数据实际落在目标盘,从而释放 C 盘空间。不重装软件。
何时使用
- 用户说"WorkBuddy 装在 C 盘太占空间""把 .workbuddy 移到 D 盘""C 盘满了,迁移 WorkBuddy 数据"等。
- 任何要把 WorkBuddy 用户数据从系统盘挪到非系统盘的需求。
前提确认(先只读检查)
- 确认
%USERPROFILE%\.workbuddy存在并量其大小(binaries/vendor/app/workspace/plugins是大头,常 1.5~2.5 GB)。 - 确认目标盘(默认
D:\WorkBuddy)剩余空间充足。 - 确认
%USERPROFILE%\.workbuddy当前不是联接(用fsutil reparsepoint query探测),已是联接则直接报告"已完成"。
执行流程
- 生成本地迁移脚本:把下方"迁移脚本"代码块中的全部内容原样写入
D:\WorkBuddy\finish_move.bat(文本编码用 ANSI 或 UTF-8 均可,Windows 记事本保存为.bat)。如需换目标盘,只改脚本里set "DST=..."那一行。 - 验证算法(关键,不要跳过):由于沙箱禁止直接运行 .bat 且禁止
Add-Type,用 PowerShell 在临时空目录把"robocopy 同步 → D⊇S 校验 →ren改名保留 →mklink /J建联接 → 经联接读到数据"原样跑一遍,确认无误后清理临时文件。 - 交给用户执行:明确告知正确顺序——彻底退出 WorkBuddy(含关闭对话窗口)→ 按 Win+E 打开资源管理器进目标盘目录 → 双击
finish_move.bat→ 看到SUCCESS后重开软件。不要在 WorkBuddy 自身运行时尝试替用户跑最终删除/改名/建联接。 - 用户回"完成"后独立核验:运行
fsutil reparsepoint query "%USERPROFILE%\.workbuddy",应成功且Print Name指向目标盘;确认C:\Users\<用户名>\.workbuddy.migrated.bak存在(可恢复备份);经联接读取文件数与目标盘一致即为成功。
> 说明:技能包内不直接附带 .bat 文件(SkillHub 禁止上传可执行脚本),而是在调用时由 WorkBuddy 在目标机器上现场生成,再交给用户双击执行。
必须避开的坑(实战教训)
- 不要用回收站 API 删/移原目录:
Microsoft.VisualBasic.FileIO.DeleteDirectory(SendToRecycleBin)在部分机器会挂起不返回,导致脚本卡死。改用ren原地改名保留为.workbuddy.migrated.bak,数据原样可恢复。 - .bat 里 echo 文本不要带小括号:
if/else块中 echo 文本里的()会被当成代码块括号,整段解析失败、脚本崩溃。全程用goto标签跳转,所有 echo/日志文本保持零括号。 - 校验用 D⊇S,不要字节完全相等:
app\session/日志/缓存运行时易变,C/D 永远对不齐。标准改为"D 盘包含 C 盘全部文件且大小一致(D⊇S)"。 - 杀进程要杀干净:不要只
taskkill /IM WorkBuddy.exe。用 PowerShell 按$_.Name -match 'workbuddy'结束所有匹配进程(含 Helper),并做"杀 → 等 2 秒 → 复检"。 - WorkBuddy 运行时做不了最后一步:它自己运行会锁目录且持续写入。务必让用户退出后再双击 bat;脚本内置进程复检,若用户在同步期间重开则中止。
- 绝不彻底删除用户数据:保留
.migrated.bak可恢复;任一步失败即中止且不删任何东西。
收尾建议(迁移成功后告知用户)
- 可删
C:\Users\<用户名>\AppData\Local\@genieworkbuddy-desktop-updater\installer.exe(约 500 MB 更新包,可安全删,重下即恢复)。 .workbuddy.migrated.bak先保留,确认软件长期正常后再手动送回收站(不删也行,与 D 盘同一份数据,不占额外空间)。- 此后 WorkBuddy 不再持续占用 C 盘空间(读写经联接走目标盘)。
Resources
references/migration-guide.md— 完整避坑说明、失败案例与脚本逐行注释,供深度参考。- 迁移脚本正文见下方代码块。
迁移脚本(本地生成,双击执行)
把以下内容写入 D:\WorkBuddy\finish_move.bat(注意保持行尾为 CRLF,记事本默认即可)。脚本逻辑:探测是否已是联接 → 结束全部 workbuddy 进程 → robocopy 同步 → D⊇S 校验 → 改名保留为 .migrated.bak → 建目录联接。任一步失败即中止且不删数据。
@echo off
setlocal
set "SRC=%USERPROFILE%\.workbuddy"
set "DST=D:\WorkBuddy\.workbuddy"
set "BAK=%USERPROFILE%\.workbuddy.migrated.bak"
set "LOG=D:\WorkBuddy\finish_move.log"
echo ===== WorkBuddy move started %date% %time% ===== >> "%LOG%"
if not exist "%SRC%" (
echo SRC_NOT_FOUND: %SRC% does not exist >> "%LOG%"
goto END
)
fsutil reparsepoint query "%SRC%" >nul 2>&1
if %errorlevel%==0 (
echo ALREADY_DONE: %SRC% is already a junction pointing to D >> "%LOG%"
goto END
)
echo [1] closing all WorkBuddy processes >> "%LOG%"
taskkill /F /IM WorkBuddy.exe >nul 2>&1
powershell -NoProfile -Command "Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'workbuddy' } | ForEach-Object { Stop-Process -Force -Id $_.Id -ErrorAction SilentlyContinue }" >> "%LOG%" 2>&1
timeout /t 2 >nul
echo [2] syncing C to D via robocopy >> "%LOG%"
if not exist "%DST%" mkdir "%DST%"
robocopy "%SRC%" "%DST%" /E /COPY:DAT /R:2 /W:1 /NFL /NDL /NP >> "%LOG%" 2>&1
echo [3] verifying D contains all C files >> "%LOG%"
powershell -NoProfile -Command "$s=Get-ChildItem '%SRC%' -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer }; $d=Get-ChildItem '%DST%' -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer }; $m=@{}; $d|ForEach-Object { $m[$_.FullName.Substring('%DST%'.Length).TrimStart('\')]=$_.Length }; $miss=0; $bad=0; foreach($f in $s){ $r=$f.FullName.Substring('%SRC%'.Length).TrimStart('\'); if(-not $m.ContainsKey($r)){$miss++} elseif($m[$r]-ne $f.Length){$bad++} }; if($miss-eq 0 -and $bad-eq 0){exit 0}else{Write-Output ('VERIFY_FAIL missing='+$miss+' bad='+$bad);exit 1}" >> "%LOG%" 2>&1
if errorlevel 1 goto VERIFY_FAIL
echo VERIFY OK >> "%LOG%"
powershell -NoProfile -Command "if(@(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'workbuddy' }).Count -gt 0){exit 1}else{exit 0}" >nul 2>&1
if errorlevel 1 goto REOPENED
echo [4] renaming source aside as recoverable backup >> "%LOG%"
if exist "%BAK%" goto BAK_EXISTS
ren "%SRC%" ".workbuddy.migrated.bak" >> "%LOG%" 2>&1
if exist "%SRC%" goto REN_FAIL
echo [5] creating directory junction >> "%LOG%"
mklink /J "%SRC%" "%DST%" >> "%LOG%" 2>&1
fsutil reparsepoint query "%SRC%" >nul 2>&1
if errorlevel 1 goto LINK_FAIL
echo SUCCESS: %SRC% now points to %DST% >> "%LOG%"
echo ===== Finished %date% %time% ===== >> "%LOG%"
goto END
:VERIFY_FAIL
echo VERIFY_FAIL: D missing some C files - aborted, nothing deleted >> "%LOG%"
goto END
:REOPENED
echo REOPENED: WorkBuddy reopened during sync - aborted >> "%LOG%"
goto END
:BAK_EXISTS
echo BAK_EXISTS: a previous .migrated.bak exists - remove it first then re-run >> "%LOG%"
goto END
:REN_FAIL
echo REN_FAIL: could not rename source - aborted >> "%LOG%"
goto END
:LINK_FAIL
echo LINK_FAIL: junction creation failed - check %LOG% >> "%LOG%"
goto END
:END
echo Done. See %LOG%
pause