Shuery 120cf3584b fix: 清掉改造过程留下的陈旧引用(代码改了、文档还写着旧的)
这一轮做的是交付一致性检查:把这一路改掉/移走/删掉的每个符号在全仓(含文档)扫一遍。42 处命中里 38 处是正当的 —— CHANGELOG 与 ADR 里讲“原来是什么”属于历史叙述,test 里的 Assert-FileExists 是因为我只抑制了误判而没有改名。剩下的 4 处是真陈旧:

  * README 还在配置表里写着 CatalogMaxDepth,而那个配置项上一轮已经整条移除;

  * Get-BakNRetItemArchiveName 的 param 里还留着一个内联的 [int]$MaxDepth = 5(上一轮的删除按行匹配,没覆盖到这种写在同一行的参数),它已经没有任何调用方传值;

  * tools\lab\README.md 与 docs\agents\domain.md 还写着模块叫 Common.psm1。

这正是这次改造从头到尾在抓的毛病:文档承诺的东西,代码已经不做了。区别是多了一个可执行的检查 —— 符号改名/删除之后,全仓扫一遍旧名字。

验收:test.ps1 9/9 全绿(7 与 5.1)、真实清单只读冒烟 4/4。
2026-09-27 10:49:21 +08:00
2026-09-26 21:46:55 +08:00

BakNRet

把 BackupList.txt 里列出的软件 / 目录用 7-Zip 打包进 Backups/,并且能用 Restore.ps1 原样恢复的 Windows 备份工具。

  • 清单里直接写软件名即可(如 Edge),目录映射维护在 SoftwareCatalog.psd1 里。
  • 一个软件一个归档:归档名 = 软件名(Edge.7z),归档内按名录里的 Slot 分层 (<Slot>\<该路径的内容>),所以同一个软件里两个都叫 persist 的目录不会再撞在一起。
  • 清单行首 + = 仅备份、- = 仅恢复;两条路径共用同一份清单。
  • 排除 / 追加 / 加密都能写在 SoftwareCatalog.psd1 的 Slot 上,清单行里可以按条目覆盖。
  • 只依赖 PowerShell(5.1 或 7.x)与 7-Zip,运行备份/恢复不需要任何模块(只有跑 Pester 测试才需要 Pester 5)。
  • 每个归档写完后做 7z t 内容校验,先写临时文件、校验通过再原子替换。
  • 每次运行产出可核对的 Backups/manifest.json 与 logs/*.log。
  • 归档之外还保存 NTFS 安全描述符(属主 / 属组 / DACL):每个归档旁边一份 <归档名>.acl.json,恢复时按它回放。这是"恢复之后原程序还能不能读写"的关键 (C:\ProgramData 下那些靠 CREATOR OWNER 授权的目录,见「安全描述符」一节)。
  • 退出码可靠:有失败就返回 1,计划任务能正确判断成败。
  • 备份结束做孤儿归档审计:磁盘上有、但没有任何清单条目指向的归档会被点名(它们恢复不到,别误删)。
  • 恢复支持 -WhatIf / -DryRun / -VerifyOnly / -Only / -Skip;其中三种"只看不写"的模式(-WhatIf / -DryRun / -VerifyOnly)一个字节都不写。
  • 动手之前先预估本次所需空间并直接判断目标卷够不够(不够只告警、不中断)。

快速开始

# 1. 先试运行:只打印计划,不写任何文件
.\Backup.ps1 -DryRun

# 2. 正式备份
.\Backup.ps1

# 3. 强制重打(忽略"源未更新"判断)
.\Backup.ps1 -Force

# 3b. 确认可以接受"有文件被占用而没打进归档"时,允许覆盖完整归档
.\Backup.ps1 -Force -AcceptWarnings

# 4. 只备份 / 只恢复某几项(通配符匹配清单条目或归档名)
.\Backup.ps1  -Only 'Edge','OpenSSH'
.\Restore.ps1 -Only 'Edge' -Force

# 5. 恢复前先看计划(恢复会覆盖真实目录,务必先看一眼)
.\Restore.ps1 -DryRun

# 6. 只校验所有归档完整性,不解压(只读,安全)
.\Restore.ps1 -VerifyOnly

文件说明

路径 作用
SoftwareCatalog.psd1 软件名 → Slot 组的映射:每个 Slot 是一个目录/文件,以及它的排除、追加、加密、说明
BackupList.txt 备份 / 恢复共用的清单,唯一的"要处理什么"来源
BackupConfig.psd1 目录、空间阈值、校验、加密等配置
Backup.ps1 / Restore.ps1 备份 / 恢复入口
BakNRet/ 模块:BakNRet.psd1(清单,FunctionsToExport 是显式白名单)+ BakNRet.psm1(薄加载器,点源顺序只在这里出现一次)+ Public/(62 个对外函数,一函数一文件)+ Private/(内部函数与模块级状态)
test.ps1 唯一验收入口:Encode + Parse + Unit + Smoke + E2E,在 7 与 5.1 上各跑一遍
PSScriptAnalyzerSettings.psd1 静态分析配置(三条有意排除与 160 字符行长,理由见 docs/adr/0008)
CONTEXT.md 术语表(本项目里每个概念只有一个叫法)
CHANGELOG.md 变更日志
docs/adr/ 决策记录(8 条:为什么这么设计、拒绝了什么)
docs/*.md 主题文档:软件名录、清单语法、归档布局、安全描述符(从本文件拆出,便于单独引用与评审)
tools/Build-BakNRetModule.ps1 把模块的多个源文件按加载器顺序合回单文件(发布形态、代码签名时需要)
tools/Invoke-Analyzer.ps1 静态分析门禁(默认规则 + 格式规则)
Backups/ 归档与 manifest.json(已 gitignore)
logs/ 每次运行的日志(已 gitignore)
tests/ 测试:Pester 套件(*.Tests.ps1)、零依赖套件、端到端验收、真实归档恢复演练
tools/Register-BackupTask.ps1 注册 / 移除计划任务
tools/Rename-Archives.ps1 把归档名对齐到当前清单规则(默认试运行)
tools/Install-TestDependencies.ps1 把 Pester 与 PSScriptAnalyzer 装到仓库内的 .tools/(不动机器上的全局模块)

SoftwareCatalog.psd1 —— 软件名 → Slot 组

SoftwareCatalog.psd1 —— 软件名 → Slot 组

软件名录的语法:一个软件由若干 Slot 组成,每个 Slot 能写路径、排除、追加、加密与说明。详见 software-catalog.md。

BackupList.txt 语法

BackupList.txt 语法

清单每一行的完整语法:方向标记、修饰符、引号与记号边界。详见 backup-list-syntax.md。

归档布局、命名与迁移

归档布局、命名与迁移

归档内的层级、归档名怎么来、以及改名与迁移时要注意什么。详见 archive-layout.md。

安全描述符(属主 / ACL)

安全描述符(属主 / ACL)

属主与 ACL 为什么不是放进归档、而是旁挂一份 sidecar,以及恢复时怎么回放。详见 security-descriptor.md。

恢复语义

  • 每一项只解出它自己那棵子树(<Slot> / <末级名>),不会把兄弟项也复制到别的父目录下。
  • 目录项:在目标的父目录下建一个指向目标目录的 junction,让 7z 直接写穿它落地(零拷贝), 解完立刻拆掉连接点。建不出连接点(父目录里已有同名实体、目标卷不支持等)时, 退回"先解到临时目录再逐项合并"——只慢不错。
  • 文件项:解到临时目录后把文件搬到 Path 指定的位置(恢复原名)。
  • 旧布局兜底:归档里没有该 Slot 时(重构前的归档)会打印告警,退回到旧布局 (把目标的末级名直接解到目标的父目录),与重构前的恢复语义一致。
  • 不做镜像同步:目标目录里多出来的文件不会被删除。想得到"完全等于归档"的目录,请先清空目标。
  • 行首 +(仅备份)的条目不恢复;行首 -(仅恢复)的条目照常恢复。
  • 目标目录比归档新时默认跳过,需要覆盖就加 -Force。
  • -WhatIf / -DryRun 只打印计划;-VerifyOnly 只跑 7z t。 这三种模式一个字节都不写(manifest.json 也不会被碰)。
  • 加密归档取不到口令时直接失败,不会让 7z 停在控制台等输入(在计划任务里那会静默挂起)。
  • 排除规则只在下一份归档里生效:已经生成的归档不会因为改了排除表而"变干净"。

manifest.json

以归档基础名为键记录每个条目:

字段 含义
source 清单里的原始写法(软件名或路径)
resolvedSource 展开后的路径
roots 归档内真实的顶层条目名(就是 Slot 名 / 源目录名 / 追加项的归档内路径;只统计真实存在的项)。每次重新处理该条目时刷新
layouts 每个归档项的 { name, kind }(dir / file),恢复端在目标还不存在时靠它判断"该还原成目录还是文件"
catalog 名录里记录的路径(便于追溯软件名到底指向哪)
archive 归档文件名
action backed-up / skip-unchanged / missing-source / invalid-path / failed / planned
reason 跳过或失败的原因
exitCode / verified / warnings 压缩工具退出码、是否通过 7z t、当前在位归档是否有警告
attemptWarnings 本次尝试是否报了警告(与 warnings 区分:保留旧归档时前者为 true、后者仍为 false)
sourceFiles / sourceBytes / archiveBytes 源文件数、源大小、归档大小
lastSuccessAt / successCount / failCount / lastRestoreAt / encrypted 历史与安全标记

Restore.ps1 优先用 manifest 定位归档,查不到才退回"从文件名反推路径"。 如果 BackupList.txt 丢了,Restore.ps1 会优先用 manifest 里的 source 自动重建。

备份前空间预估

每次备份在动手之前先按清单顺序模拟一遍,把"这次要写多少、盘够不够"直接打出来:

[INFO] ==== 备份前空间预估(只读)====
[INFO]   目标卷可用空间:7.37 GB
[INFO]   本次要重打 8 个条目(另有 7 个源未更新会跳过、9 个源不存在)
[INFO]   新归档合计约 2.44 GB;其中会替换掉的旧归档 1.89 GB
[INFO]     - Edge                   源  2,303.8 MB /  11354 文件   现有 1,781.3 MB   预估 2,303.8 MB
[INFO]     - MiFlash_Unlock         源    231.3 MB /    153 文件   现有    70.0 MB   预估    91.0 MB
[INFO]   ...
[INFO]   预计峰值新增占用:2.25 GB(全程净增量 0.55 GB)
[INFO]   结论:空间足够(预计用 2.25 GB / 可用 7.37 GB)
[INFO] ============================
  • 估算模型:临时归档写完时旧归档还在,那一刻占用"当前累计净增量 + 本次预估", 原子替换之后本次净增量 = 预估 − 旧归档大小。峰值取整个过程的最大值。
  • 预估归档大小:有历史归档时取 min(源大小, 旧归档 × 1.3);没有历史归档时按 "完全不压缩"的悲观值估 —— 宁可报多不报少。
  • 结论只有两种:空间足够,或者"空间可能不够!预计需要 X GB,可用 Y GB,差 Z GB"。 不够时只告警、不中断 —— 真正放不下的条目会被逐条目守卫跳过;想稳妥就腾空间或加 -Only / -Skip 分批。
  • 这一步是只读的,不改任何文件;-DryRun 也照跑。

日志

logs/backup-<时间戳>.log / logs/restore-<时间戳>.log,与控制台内容一致。 压缩工具自身的实时输出直接进控制台,不进日志(见「设计取舍」)。

配置(BackupConfig.psd1)

@{
    BackupDir        = 'Backups'      # 相对路径按脚本所在目录解析
    LogDir           = 'logs'
    SnapshotDir      = 'Backups\snapshots'
    SoftwareCatalog  = 'SoftwareCatalog.psd1'
    MinFreeSpaceGB   = 5
    VerifyArchive    = $true          # 归档后跑 7z t
    ComputeHash      = $false         # 是否额外算 SHA256(大归档很慢)
    CompressionLevel = 9
    ToolOutput       = 'live'         # live | quiet
    Snapshot         = @{ Enabled = $false; KeepCount = 3; KeepDays = 30 }
    Encryption       = @{ Enabled = $false; PasswordFile = ''; EncryptHeaders = $true }
    DefaultExcludes  = @('!Thumbs.db', '!desktop.ini')
}

优先级:命令行参数 > BackupConfig.psd1 > 代码内置默认值,也可以用 -ConfigPath 指定其它配置文件。

加密

默认关闭 —— 一旦开启而口令丢失,备份就再也解不开。

# 方式一:给某个 Slot 加密(私钥、浏览器数据这类最典型)
#   SoftwareCatalog.psd1:
#       OpenSSH = @{ DefaultData = @{ Path = '%UserProfile%\.ssh'; Encrypt = $true } }
#   或在 BackupList.txt 的条目上写:
#       Edge :encrypt
#       PowerShell @ Encrypt='$false'      # 反过来,关掉名录里的默认加密

# 方式二:全部加密,改配置
#   Encryption = @{ Enabled = $true; PasswordFile = 'D:\secret\baknret.key' }

# 口令来源(二者取其一)
$env:BAKNRET_PASSWORD = '...'      # 或
.\Backup.ps1 -KeyFile 'D:\secret\baknret.key'   # 文件首行即口令

一个软件一个归档:名录里各 Slot 的 Encrypt 不一致时,整个归档按加密处理(宁可多加密,不可漏加密), 并打印告警。要求加密但取不到口令时,该条目会明确失败,绝不会退化成明文归档。 恢复加密归档时同理:取不到口令就直接失败,不会让 7z 停在控制台等待输入(在计划任务里那会静默挂起)。

⚠️ 7-Zip 只接受命令行口令,口令在本机进程列表里会短暂可见。这是 7z 本身的限制,请自行权衡。

口令放在哪里

口令文件必须放在仓库之外。 BackupConfig.psd1 的 PasswordFile 默认留空,这不是遗漏: 出厂默认值指向仓库里的某个文件,等于鼓励把口令提交进版本库。.gitignore 里排除 *.key 只是 第二道防线。三种给它口令的方式:

powershell
# A. 环境变量(计划任务用这个最省事)
$env:BAKNRET_PASSWORD = '...'

# B. 配置文件里指到一个仓库外的文件
Encryption = @{ Enabled = $false; PasswordFile = (Join-Path $env:USERPROFILE '.baknret.key'); EncryptHeaders = $true }

# C. 单次指定
.\Backup.ps1 -KeyFile (Join-Path $env:USERPROFILE '.baknret.key')

取不到口令时,加密条目明确失败,绝不退化成明文归档 —— 这条行为没有放宽。

从旧版本迁移的人注意:如果口令文件原先就放在仓库根(baknret.key),请把它移到仓库之外, 再按上面任一种方式指过去。搬之前可以先用 pwsh -File .\Restore.ps1 -VerifyOnly -Only "WindowsTerminal" -KeyFile <新路径> 验一下口令对不对(那份归档是加密的,口令不对会报错)。

计划任务

.\tools\Register-BackupTask.ps1 -At '21:30' -DryRun   # 先看将要注册什么
.\tools\Register-BackupTask.ps1 -At '21:30'            # 注册
.\tools\Register-BackupTask.ps1 -Remove                # 移除

任务调用 Backup.ps1,脚本自身写日志并按失败数返回退出码,所以「上次运行结果」在任务计划程序里可读。

测试

四套,按"需要多少依赖"分层:

套件 命令 需要什么 覆盖
Pester 套件(推荐) .\tests\Run-Pester.ps1 Pester 5.0+ 与 7z 150 项:清单语法(方向 / :: / :- / :+ / :encrypt / @ Key='值' / 整行引号与记号边界)、Slot 结构名录、归档命名、排除翻译(-x! / -xr! / !re:)、Slot 前缀分配、暂存、manifest / 配置 / 名录,外加用子进程真正跑 Backup.ps1 / Restore.ps1 的端到端与回归
零依赖套件 .\tests\Run-Tests.ps1 只要 PowerShell + 7z 101 项:同样的单元面,适合没装 Pester 的机器
端到端验收 .\tests\Run-E2E.ps1 只要 PowerShell + 7z 36 项:备份 → 确认排除生效 → 删源 → 恢复 → 逐字节对拍,含 <Slot>\ 布局、文件 Slot、方向标记与旧布局回退
真实归档恢复演练 .\tests\Restore-Drill.ps1 只要 PowerShell + 7z 12 个真实归档:解到临时目录再和活源逐字节对拍(全程不碰真实目录)
安全描述符套件 .\tests\Run-Pester.ps1(内含 BakNRet.Security.Tests.ps1) Pester 5 + 7z 25 项:排除判定与 7z -x!/-xr! 语义对齐、SID 映射边界(前缀 SID 不被误伤)、采集与 sidecar 往返、回放(CREATOR OWNER + 孤儿 SID + protected 逐字节一致)、以及真的用子进程跑 Backup.ps1/Restore.ps1 做端到端

演练会把"源在备份之后变过"和"归档/解压有问题"分开:内容不一致时看活源文件的修改时间, 晚于归档时间就算"源变了"(只提示),不晚于归档时间却内容不同才算失败。真实机器上的归档 常常是几周前的,不这样区分就天天报假失败。

Pester 套件要求 5.0+。系统自带的是 3.4.0,没有 Should -Be,套件会直接语法错误, 所以 Run-Pester.ps1 会先查版本,查不到就以退出码 2 结束并打印安装命令。两种装法:

.\tools\Install-TestDependencies.ps1                  # 只装进仓库内的 .tools/(推荐,不动机器上的全局模块)
# 或者
Install-Module Pester -Scope CurrentUser -MinimumVersion 5.0.0

Run-Pester.ps1 优先使用 .tools/ 里的本地副本,其次是机器上已装的 5.x; .tools/ 已进 .gitignore。

Pester 套件里的端到端用例是用子进程跑 Backup.ps1 / Restore.ps1 的,原因有二: 两个脚本结尾都会 exit,同进程 & 调用会把 Pester 宿主一起带走;而且子进程给出的是 真正的进程退出码,正好独立验证"退出码取法"这条修复。

相对旧版修了什么

见 CHANGELOG.md。本次改造(2026-09)的修复、结构变化与规范落地都在那里。

验收与静态分析

一条命令跑完全部验收层次,两个 PowerShell 版本各跑一遍:

powershell
.\test.ps1                       # Encode + Parse + Unit + Smoke + E2E
.\test.ps1 -Suite Parse          # 只跑一层
.\test.ps1 -Suite E2E -PSVersion 5.1
.\tests\Run-RealSmoke.ps1        # 拿真实清单与真实归档做只读冒烟
层次 内容 依赖
Encode 受管文件的 BOM / 行尾 / 制表符 —— 丢了 BOM 只在 5.1 上出错,所以这条必须单独检查 无
Parse 全仓 .ps1 / .psm1 / .psd1 在 5.1 与 7 上解析零错 无
Unit Pester 套件(单元面) Pester 5+
Smoke 零依赖套件(关键冒烟) PowerShell + 7z
E2E 真的调用 7z 打包 → 删源 → 恢复 → 逐字节对拍 7z
Drill 真实归档恢复演练(只读,要显式点名) 本机真实归档

Run-RealSmoke.ps1 刻意独立于上面几层:其它套件都在临时目录里自造夹具,跑得快、可重复; 它专门跑真实清单,用来挡住"夹具全绿、真实数据全废"。它检查四件事:方向标记全部被剥掉、 没有软件名退化成"名录里没有"、两个只读模式退出 0、manifest.json 的 SHA256 前后不变。

静态分析是独立门禁,不塞进上面几层(套件跑一次二十多秒,混进去会让"测试红了"这句话 失去分辨力):

powershell
.\tools\Invoke-Analyzer.ps1          # 默认规则 + 格式规则
.\tools\Invoke-Analyzer.ps1 -Quiet   # 只看按规则汇总

那 6 条格式规则(括号、缩进、空格、对齐、大小写)在 PSScriptAnalyzer 里默认是 Disabled —— 不带 -Settings 的 Invoke-ScriptAnalyzer -Severity Warning,Error 会静默漏掉全部排版问题。 三条有意排除与 160 字符行长上限的理由见 docs/adr/0008。

设计取舍(有意为之,不是遗漏)

  • 放弃 7z 的更新模式(u)。 7z 默认固实压缩,u 本来就要重压大部分数据,收益很小,却让"排除规则改动"和"源里删掉的文件"永远进不了归档。
  • 包内用 Slot 分层,靠暂存目录改名。 7z 没有"入库时改名"的能力,所以打包前建一个暂存目录, 把每个归档项按包内名字挂进去(目录走 junction、文件走硬链接/复制),打完立刻拆掉。 代价是每份归档多一次 junction 开销;收益是一个软件可以有多个目录而不怕重名 (scoop 的用户 persist 与全局 persist 就属于这种),恢复时也能精确地"只解这一棵子树"。 建不出连接点时明确报错,不悄悄退化成另一种布局。顺带一提,7z 的 -spf 不是干这个的 (它是 use fully qualified file paths)。
  • 恢复用 junction 零拷贝落地。 目标父目录下建一个指向目标的 junction,让 7z 直接写穿它, 解完立刻拆掉;建不出来就退回"先解到临时目录再合并"。这样不必把大归档整体搬两遍。
  • 不捕获压缩工具的输出。 结构化记录交给日志与 manifest.json;捕获子进程 stdio 需要额外管道,在受限环境里会直接失败。
  • 有警告(退出码 1)时不覆盖完整的归档。 被占用的文件会让 7z 返回 1,此时新归档是不完整的。实测 Edge 运行时打包,118 个文件读不到,其中包含 Login Data(密码)、Cookies、History、Web Data。所以在位归档完整时脚本保留它、报失败、退出码 1,确认可以接受再显式加 -AcceptWarnings。
  • 名录里的路径不存在时,恢复仍然可用。 源被删掉正是要恢复的场景,所以解析器照旧给出 Items,备份端则据此跳过。
  • 源路径不存在只算"跳过",不算失败。 会以 missing-source 记进 manifest。失败只统计真正打不开的条目。
  • @ Path= 覆盖只允许单 Slot 条目。 多 Slot 时"覆盖"根本没有唯一含义,直接报错比猜一个 Slot 好。
  • 旧归档用"旧布局兜底"而不是拒绝恢复。 重构前的归档包内没有 Slot 层, 恢复时按 Slot 解会失败,脚本捕获后按旧布局(目标的末级名)再试一次, 并在日志里说清楚——旧备份仍然救得回来。

已知限制

  • 改软件名 / 改 Slot 名等于换归档结构。 改名后旧归档不会被自动迁移,用 tools/Rename-Archives.ps1 或手动改名, 并注意 manifest 里会留下旧键;Slot 名变了则需要重打(-Force)。
  • 路径里本来就含 + 或 _from_ 时,仅靠文件名无法可靠反推路径,此时依赖 manifest.json。
  • -Snapshot 目前是"复制一份带时间戳的副本",不做自动轮转清理(KeepCount / KeepDays 尚未实现)。
  • 加密归档的常规备份/恢复不依赖 RAR;RAR 与内置 ZIP 分支仅作降级,未做加密支持(ZIP 明确拒绝加密请求)。 内置 ZIP 分支也不支持排除规则(Compress-Archive 没有对应开关),只保证内容完整。
  • 暂存改名需要能建目录连接点(junction)。 暂存目录在 %TEMP%(NTFS 即可),目标源目录跨盘也没问题; 建不出连接点时该条目会明确失败,而不会静默换成别的布局。恢复时的 junction 建不出来会自动退回"临时目录 + 合并"。
  • 一个 Slot 只能对应一个目录。 前缀补全命中多个候选(同名目录分散在多处)时会报错并让你拆成多个 Slot, 而不是任选一个。
  • !re: 有量级上限。 正则命中的路径超过 300 条、或排除参数超过命令行安全长度时会明确失败; 这种场景应改用更粗的通配模式。
  • root=<名> 标记已废弃。 包内的一层目录现在由 Slot 决定;写了该标记只会打印告警。
  • 空间只做"预估 + 提示",不做全局拦截。 备份前会打印预计峰值新增和"够不够"的结论; 不够时只告警不中断,真正放不下的条目交给逐条目守卫跳过。MinFreeSpaceGB 是告警阈值。 想稳妥跑完就先腾空间,或用 -Only / -Skip 分批。
  • 恢复安全描述符需要管理员(或 SYSTEM)。 非提权时属主写不进去(SeRestorePrivilege 不在令牌里),脚本会退化到"只恢复 DACL"并明确告警 —— 那不是失败,但 CREATOR OWNER 会判给"当前属主",所以依赖它的程序可能仍然没权限。
  • acl.json 要跟归档一起搬。 它不在归档里(7z 装不下),改名 / 迁移归档时要用 tools\Rename-Archives.ps1 或手工把同名旁挂文件一起改。
  • 7z 会跟随 junction(不是存成链接,因为 -snl 只对 WIM/TAR 生效):所以 scoop 那种 apps\<app>\current 的连接点,备份时会把目标内容一并收进归档(体积翻倍),恢复后 current 变成真实目录。功能上仍然可用(current\bin\... 路径还在),但要心里有数。
  • 跨机恢复要配 Security.SidMap:本机不存在的 SID 写进 DACL 是安全的(那条 ACE 只是 永不匹配),但写进属主会让谁都没有合理所有权 —— 换域 / 换机时请给映射,或接受 "属主未恢复"的告警。服务账户(NT SERVICE\X)的 SID 是按名字算出来的,跨机一致。
S
Description
No description provided
Readme Apache-2.0
1.6 MiB
0 Stars 1 Watchers 0 Forks
Languages
PowerShell 100%