Files
BakNRet/BakNRet/Public/Enable-BaknretPrivilege.ps1
T
Shuery 42f02d0eca refactor: 公共面补 BakNRet 前缀,产品名大小写全仓统一
16 个没有前缀的公共函数补上 BakNRet(Write-Log → Write-BakNRetLog、Resolve-BackupEntry →
Resolve-BakNRetBackupEntry、Find-ChildDirectoryByName → Find-BakNRetChildDirectoryByName 等),
另外把全仓的 Baknret 统一成 BakNRet(47 个文件、940 处、65 个定义文件重命名)。

这不是审美问题:静态分析直接拓出一条实据 —— Write-Log 与本机某个已装模块导出的命令
**重名**(PSAvoidOverwritingBuiltInCmdlets),而重名的后果是导入两个模块时有一方的命令被
静默遮蔽。补前缀正是这条规则的解法,改名后它归零。

为什么敢做这个规模:PowerShell 的函数名解析大小写不敏感,所以 Baknret → BakNRet 在功能
上是零风险;真正要验证的是 16 个补前缀的调用点,而 276 个断言几乎覆盖了每个函数。另外
"名字与文件名一致"这条不变式有断言盯着(加载器点源的文件集合 vs 磁盘)。

踩到并记下的坑:Windows 文件系统大小写不敏感,所以**只改大小写**的重命名会被 Move-Item
当成同一个文件而静默跳过 —— 同一批里同时改了名字的那 16 个文件却成功了,于是"看起来能跑"。
最后用"先移到临时名、再移到目标名"的两步走解决,判断与替换全部改用显式大小写敏感的形式
(-creplace / -cmatch)。

顺带把名录指纹缓存从 MD5 换成 SHA256(PSAvoidUsingBrokenHashAlgorithms):它只是缓存键,
没有兼容负担。

验收:test.ps1 9/9 全绿(7 与 5.1)、100 个文件两版解析零错、276 个断言全过、
构建工具仍能合回单文件(3300 行)。
2026-09-27 09:56:36 +08:00

99 lines
4.3 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function Enable-BakNRetPrivilege {
<#
.SYNOPSIS
在当前进程令牌里启用指定特权,返回哪些没能启用。
.DESCRIPTION
必须显式启用。MSDN(SetNamedSecurityInfoW)写明:
"If the caller does not have the SeRestorePrivilege constant, this SID must be
contained in the caller's token, and must have the SE_GROUP_OWNER permission
enabled." 也就是说没有它就没法把属主改成别的账户,而失败信息只有一句
"Access is denied"(easily mistaken for a path problem)。
两个坑:
* 结构体嵌套赋值(`$tp.Privileges.Luid.LowPart = …`)在 PowerShell 里改的是
装箱副本,改了不生效,所以整段放进 C# 里做;
* AdjustTokenPrivileges 返回 true 也可能是 ERROR_NOT_ALL_ASSIGNED(1300),
那代表特权根本不在令牌里,必须当成失败。
返回 [pscustomobject]@{ Enabled; Missing; Failed }(都是名字数组)。
#>
param([string[]]$Name = @('SeRestorePrivilege', 'SeBackupPrivilege'))
$result = [pscustomobject]@{
Enabled = @()
Missing = @()
Failed = @()
}
if (-not ('BakNRet.Privileges' -as [type])) {
try {
Add-Type -Namespace BakNRet -Name Privileges -MemberDefinition @'
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool OpenProcessToken(IntPtr h, int acc, out IntPtr phtok);
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LookupPrivilegeValue(string host, string name, out long pluid);
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
ref TOKEN_PRIVILEGES newst, int len, IntPtr prev, IntPtr relen);
[DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr h);
[StructLayout(LayoutKind.Sequential)] public struct LUID { public uint LowPart; public int HighPart; }
[StructLayout(LayoutKind.Sequential)] public struct LUID_AND_ATTRIBUTES { public LUID Luid; public uint Attributes; }
[StructLayout(LayoutKind.Sequential)] public struct TOKEN_PRIVILEGES { public uint PrivilegeCount; public LUID_AND_ATTRIBUTES Privileges; }
// 0 = 已启用;1 = 令牌里没有这个特权;2 = 其它失败
public static int Enable(string name) {
IntPtr token;
if (!OpenProcessToken(GetCurrentProcess(), 0x28, out token)) { return 2; }
try {
long luid;
if (!LookupPrivilegeValue(null, name, out luid)) { return 1; }
TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES();
tp.PrivilegeCount = 1;
tp.Privileges.Luid.LowPart = (uint)(luid & 0xFFFFFFFF);
tp.Privileges.Luid.HighPart = (int)(luid >> 32);
tp.Privileges.Attributes = 0x2;
if (!AdjustTokenPrivileges(token, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero)) { return 2; }
if (Marshal.GetLastWin32Error() == 1300) { return 1; }
return 0;
} finally { CloseHandle(token); }
}
'@
}
catch {
Write-BakNRetLog "特权启用代码编译失败(本次不启用任何特权):$($_.Exception.Message)" -Level WARN
$result.Failed = @($Name)
return $result
}
}
$enabled = @(); $missing = @(); $failed = @()
foreach ($privilege in @($Name)) {
$cacheKey = $privilege
if ($script:BakNRetPrivilegeState.ContainsKey($cacheKey)) {
$state = $script:BakNRetPrivilegeState[$cacheKey]
}
else {
$state = [BakNRet.Privileges]::Enable($privilege)
$script:BakNRetPrivilegeState[$cacheKey] = $state
}
switch ($state) {
0 { $enabled += $privilege }
1 { $missing += $privilege }
default { $failed += $privilege }
}
}
if ($missing.Count -gt 0) {
Write-BakNRetLog ("这些特权不在当前令牌里(需要管理员或 SYSTEM):{0} —— 属主将无法改成别的账户,只能恢复 DACL" -f ($missing -join '、')) -Level WARN
}
if ($failed.Count -gt 0) {
Write-BakNRetLog ("这些特权启用失败:{0}" -f ($failed -join '、')) -Level WARN
}
$result.Enabled = @($enabled)
$result.Missing = @($missing)
$result.Failed = @($failed)
return $result
}