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 行)。
99 lines
4.3 KiB
PowerShell
99 lines
4.3 KiB
PowerShell
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
|
||
}
|