AD 组策略不生效排查
AD 组策略不生效排查
gpupdate 返回成功,却没有得到预期设置,这句话背后至少有几种不同情况:整份 GPO 没进入适用范围,客户端读不到它,配置分支被禁用,或者 GPO 已经处理、只有某个首选项被跳过。看到同一个现象就重新加域或扩大权限,往往会改错地方。
排查环境为 Windows Server 2022 Datacenter,域控 LAB-DC1、成员机 LAB-MEMBER,域名 corp.test.checo.cc,使用 Windows PowerShell 5.1。下面分别检查权限、备份恢复、WMI 筛选、配置分支和首选项目标定位;测试使用专属 GPO、OU 和注册表标记。
先在真正受影响的客户端保存本轮结果。计算机配置使用提升会话;用户配置要在目标用户的真实会话中检查,不能拿管理员的 HKCU 代替。
$t = Get-Date
whoami /user
gpupdate /target:computer /force /wait:120
$updateExit = $LASTEXITCODE
gpresult /scope computer /r
$resultExit = $LASTEXITCODE
"GPUpdateExit=$updateExit; GPResultExit=$resultExit"
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-GroupPolicy/Operational'
StartTime=$t
} | Select-Object TimeCreated,Id,RecordId,Message/wait 是命令等待期限。若输出说明后台仍在处理,等到本轮完成再判断,避免一轮未结束又发起另一轮。报告、事件和实际配置应对应同一时间窗口。
1. 判断失败阶段
| 本轮观察 | 先检查什么 |
|---|---|
| GPO 完全不在用户报告中 | 对象位置、链接、用户授权以及计算机读取通道 |
Denied (Security) | 有效 Read/Apply、显式拒绝、当前组身份 |
Denied (WMI Filter) | 关联的完整 WQL 与目标机实际属性 |
链接正常,Not Applied (Unknown Reason) | 结合 GpoStatus、配置分支及日志,不能只凭文字归因 |
| GPO 已在 Applied 中,同一 GPO 只有部分项目生效 | 首选项动作、禁用状态、项目级目标和实际路径 |
这些是入口,不是错误文字与唯一根因的一一映射。后面的实验里,无 Read 与无 Apply 都可能显示 Denied (Security);计算机缺少用户 GPO 的 Read 时,报告甚至没有把它列为被拒绝项。
2. 安全筛选
权限实验把 GpoApply 授予 KB225-Computers,Authenticated Users 保留 GpoRead。这样能读到 GPO 与有权应用 GPO 就可以分别观察。
| 条件 | 计算机侧观察 | ComputerMarker |
|---|---|---|
| 未加入目标组 | Denied (Security) | ABSENT |
| 加组后只刷新策略 | 仍被拒绝,报告组列表没有新组 | ABSENT |
| 加组后重启再刷新 | 目标组及 GPO 出现 | ReadApplyOK |
| 同一目标组从 Apply 降为 Read | Denied (Security) | ABSENT |
后两组没有改变链接或 GPO 设置。组成员变更需要当前客户端身份更新,而 GPO ACL 改动是另一件事。本次用重启取得可重复的身份正对照;不能把反复运行 gpupdate 当作重建登录身份。
修改前,在管理机选择正确 GPO、域和 DC,保存备份、报告、权限及 OU 链接。以下是针对已存在实验对象的管理示例,不会创建整套实验环境:
$ErrorActionPreference = 'Stop'
Import-Module ActiveDirectory
Import-Module GroupPolicy
$dc = 'LAB-DC1.corp.test.checo.cc'
$domain = 'corp.test.checo.cc'
$d = Get-ADDomain -Server $dc
if ($d.DNSRoot -ne $domain) { throw 'Domain mismatch' }
$gpoName = 'KB225-ComputerFilter'
$g = Get-GPO -Name $gpoName -Domain $domain -Server $dc
$c = Get-ADComputer LAB-MEMBER -Server $dc -Properties memberOf
$c | Select-Object DistinguishedName,ObjectGUID,memberOf
$backup = 'C:\GPO-Permission-Backup\' + (Get-Date -Format yyyyMMdd-HHmmss)
New-Item -ItemType Directory -Path $backup | Out-Null
$b = Backup-GPO -Guid $g.Id -Path $backup -Domain $domain -Server $dc
$b | Export-Clixml ($backup + '\backup-instance.xml')
Get-GPOReport -Guid $g.Id -ReportType Xml -Path ($backup + '\gpo-before.xml') -Domain $domain -Server $dc
Get-GPPermission -Guid $g.Id -All -Domain $domain -Server $dc |
Select-Object @{Name='Trustee';Expression={$_.Trustee.Name}},Permission
Get-ADOrganizationalUnit -Filter * -Server $dc -Properties gPLink,gPOptions |
Select-Object DistinguishedName,ObjectGUID,gPLink,gPOptions |
Export-Clixml ($backup + '\links-before.xml')目标组授 Apply,Authenticated Users 降为 Read:
Set-GPPermission -Guid $g.Id -TargetName 'KB225-Computers' -TargetType Group -PermissionLevel GpoApply -Domain $domain -Server $dc
Set-GPPermission -Guid $g.Id -TargetName 'Authenticated Users' -TargetType Group -PermissionLevel GpoRead -Replace -Domain $domain -Server $dc
Get-GPPermission -Guid $g.Id -All -Domain $domain -Server $dc |
Select-Object @{Name='Trustee';Expression={$_.Trustee.Name}},@{Name='Permission';Expression={$_.Permission.ToString()}}降权时的 -Replace 很重要:已有较高权限可能在未指定替换时保留。命令成功后仍要回读。不要把一个实际应限制范围的 GPO 重新授给所有人 Apply。
在成员机刷新并验收:
gpupdate /target:computer /force /wait:120
$updateExit = $LASTEXITCODE
gpresult /scope computer /r
$resultExit = $LASTEXITCODE
Get-ItemProperty 'HKLM:\Software\Policies\KB225' -Name ComputerMarker
"GPUpdateExit=$updateExit; GPResultExit=$resultExit"故意把目标组降为只读的对照如下,完成后应恢复预定权限:
Set-GPPermission -Guid $g.Id -TargetName 'KB225-Computers' -TargetType Group -PermissionLevel GpoRead -Replace -Domain $domain -Server $dc
Get-GPPermission -Guid $g.Id -All -Domain $domain -Server $dc该阶段 gpupdate、gpresult 都返回 0,安全通道为 True,但 marker 缺失。因此退出码和安全通道不能代替策略结果检查。
计算机读取权限
另一份用户 GPO 给普通用户 KB225-User 授了 Apply,但删除 Authenticated Users 后,成员计算机没有 Read。真实用户会话中,适用列表是 N/A,UserMarker=ABSENT;Operational 5312 的列表为 None,5311 为 No loopback mode。
保留用户权限与会话,仅增加计算机组 Read:
$ug = Get-GPO -Name 'KB225-UserRead' -Domain $domain -Server $dc
$ubackup = 'C:\GPO-Permission-Backup\User-' + (Get-Date -Format yyyyMMdd-HHmmss)
New-Item -ItemType Directory -Path $ubackup | Out-Null
$ub = Backup-GPO -Guid $ug.Id -Path $ubackup -Domain $domain -Server $dc
$ub | Export-Clixml ($ubackup + '\backup-instance.xml')
Set-GPPermission -Guid $ug.Id -TargetName 'KB225-Computers' -TargetType Group -PermissionLevel GpoRead -Domain $domain -Server $dc
Get-GPPermission -Guid $ug.Id -All -Domain $domain -Server $dc |
Select-Object @{Name='Trustee';Expression={$_.Trustee.Name}},@{Name='Permission';Expression={$_.Permission.ToString()}}随后仍在该普通用户自己的窗口中执行:
whoami /user
whoami /groups
gpupdate /target:user /force /wait:120
$updateExit = $LASTEXITCODE
gpresult /scope user /r
$resultExit = $LASTEXITCODE
gpresult /scope user /h "$env:TEMP\KB225-user-result.html" /f
Get-ItemProperty 'HKCU:\Software\Policies\KB225' -Name UserMarker
"GPUpdateExit=$updateExit; GPResultExit=$resultExit"结果变为 GPO 出现在 Applied、UserMarker=UserReadOK,两条命令退出码均为 0;事件 4017 开始读取该 GPO 的 gpt.ini。这与 MS16-072 后由计算机安全上下文取回用户策略的机制一致。微软对此读取权限的说明
在 GPMC 的委派页补 Read 即可,不需要把计算机或所有用户重新加入安全筛选的 Apply 范围。域控上的 Enterprise Domain Controllers 默认 Read 还可能掩盖问题,因此这里特意使用普通成员机和非管理员用户测试。
3. 恢复与作用域
恢复实验使用 HKLM\Software\Policies\KB08Restore\Marker。正常值为 Healthy-v1。随后把内容改成 Broken-v2、Authenticated Users 降为 Read,并移除 OU 链接。
| 阶段 | GPO 内容 | 授权 | OU 链接 | 客户端值 |
|---|---|---|---|---|
| 正常 | Healthy-v1 | Apply | 启用 | Healthy-v1 |
| 改坏后 | Broken-v2 | Read | 空 | 缺失 |
| Restore 后 | Healthy-v1 | Apply | 仍为空 | 缺失 |
| 单独补回链接 | Healthy-v1 | Apply | 启用 | Healthy-v1 |
GPO 的目录容器与 SYSVOL 模板保存策略本身;OU 的 gPLink、gPOptions 保存链接和继承信息。因此 GPO 备份与容器链接记录要分开保存。
Backup-GPO 输出的 GpoId 是 GPO GUID,Id 是这份备份的 BackupId。同一 GPO 可以有多份备份。选择历史版本时应指明 BackupId,避免恢复备份目录中较新的错误版本:
GpoId : 3811e645-2285-4528-9403-f67af2c12b2e
Id : 9e9eabdc-1c35-4777-8597-46595197d27e管理示例先为当前版本备份,恢复后再读权限和链接。这里的 $gpoId、$ou、$root 分别表示已确认的目标 GUID、目标 OU DN 和本次备份目录:
Import-Module ActiveDirectory,GroupPolicy
$domain = 'corp.test.checo.cc'
$dc = 'LAB-DC1.corp.test.checo.cc'
$gpoId = [guid](Read-Host 'Affected GPO GUID')
$ou = Read-Host 'Target OU DN'
$root = 'C:\GpoRecovery-' + (Get-Date -Format yyyyMMdd-HHmmss)
New-Item -ItemType Directory -Path $root -ErrorAction Stop | Out-Null
$goodPath = Read-Host '输入正常 GPO 备份所在根目录'
$backupId = [guid](Read-Host '输入正常备份的 BackupId')
$currentPath = Join-Path $root 'before-restore'
New-Item -Path $currentPath -ItemType Directory -ErrorAction Stop | Out-Null
Backup-GPO -Guid $gpoId -Path $currentPath -Domain $domain -Server $dc | Out-Null
Restore-GPO -BackupId $backupId -Path $goodPath -Domain $domain -Server $dc -Confirm:$false
Get-GPO -Guid $gpoId -Domain $domain -Server $dc | Format-List
Get-GPPermission -Guid $gpoId -All -Domain $domain -Server $dc | Format-Table
$links = (Get-GPInheritance -Target $ou -Domain $domain -Server $dc).GpoLinks
$links | Format-Table DisplayName,GpoId,Enabled,Enforced,Order只有在原链接记录确认应存在而当前确实缺失时补链;原本是否 Enforced、Order 几号,都应按该条记录恢复:
$existing = @($links | Where-Object GpoId -eq $gpoId)
if ($existing.Count -eq 0) {
New-GPLink -Guid $gpoId -Target $ou -LinkEnabled Yes -Enforced No -Order 1 -Domain $domain -Server $dc
}
Get-ADOrganizationalUnit -Identity $ou -Server $dc -Properties gPLink,gPOptions |
Format-List DistinguishedName,gPLink,gPOptions本例恢复后 GPO GUID 不变,用户版本 1、计算机版本 3,AD versionNumber 与 GPT.INI 均为 65539。恢复推进了版本,不应要求版本数字退回备份时的旧值。
Import 是另一种操作。源备份导入已存在目标后,目标内容变成 Healthy-v1,但目标自己的 GUID、只读授权、禁用链接仍保留。启用链接后仍 Denied (Security),补上目标应有的 Apply 后才恢复。恢复原对象与向另一对象导入不能混为一谈。Restore-GPO
4. WMI 筛选
WMI 实验只把条件从 ProductType=3 改为 ProductType=1,同一 GPO、链接、ACL 和配置保持不变。LAB-MEMBER 上前者返回一个对象,后者返回零个,结果如下:
| 查询条件 | 本机对象数 | 策略结果 | Marker |
|---|---|---|---|
| ProductType=3 | 1 | Applied | WmiApplied |
| ProductType=1 | 0 | Denied (WMI Filter) | ABSENT |
| 改回 ProductType=3 | 1 | Applied | WmiApplied |
ProductType 区分客户端 1、域控 2、非域控服务器 3,不区分 Standard/Datacenter。测试机显示 Server 2022,但 WMI Version 为 10.0.20348,BuildNumber 为字符串 20348;按产品显示名猜查询前缀容易筛错机器。
下面在受影响客户端同时保存完整查询、本机结果和本轮策略事件。不要在管理机或 DC 上执行完就把结果当成成员机证据:
$ErrorActionPreference='Stop'
$start=Get-Date
$os=Get-CimInstance Win32_OperatingSystem
$good='SELECT * FROM Win32_OperatingSystem WHERE ProductType=3'
$bad='SELECT * FROM Win32_OperatingSystem WHERE ProductType=1'
$goodCount=@(Get-CimInstance -Namespace root\CIMv2 -Query $good).Count
$badCount=@(Get-CimInstance -Namespace root\CIMv2 -Query $bad).Count
$update=gpupdate /target:computer /force /wait:120 2>&1
$updateExit=$LASTEXITCODE
$result=gpresult /scope computer /z 2>&1
$resultExit=$LASTEXITCODE
if($updateExit -ne 0 -or $resultExit -ne 0){throw "GP command failed $updateExit $resultExit"}
$key='HKLM:\Software\Policies\KBEAD141011'
$marker='ABSENT'
if(Test-Path $key){$item=Get-Item $key;if($item.GetValueNames() -contains 'Marker'){$marker=$item.GetValue('Marker')}}
@{Start=$start.ToUniversalTime().ToString('o');End=(Get-Date).ToUniversalTime().ToString('o');OS=$os.Caption;Version=$os.Version;ProductType=$os.ProductType;GoodQueryCount=$goodCount;BadQueryCount=$badCount;GPUpdateExit=$updateExit;GPResultExit=$resultExit;Marker=$marker;Update=@($update);Result=@($result);Events=@(Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-GroupPolicy/Operational';StartTime=$start} -ErrorAction SilentlyContinue|Select-Object TimeCreated,Id,RecordId,Message)}|ConvertTo-Json -Depth 6修改前导出 WMI 筛选定义,记录命名空间、全部查询和关联 GPO。筛选可以复用,调整共享筛选会影响其他关联对象;必要时创建专用筛选,而不是简单取消筛选扩大范围。修复后仍应从客户端实际使用的 DC 核对定义及 GPO 关联,避免 AD/SYSVOL 传播中读到不同版本。
5. 禁用的配置分支
分支实验的策略文件哈希、ACL 和链接保持不变,仅改变 GpoStatus:
| GpoStatus | flags | 用户配置 | 计算机配置 |
|---|---|---|---|
| AllSettingsEnabled | 0 | 启用 | 启用 |
| UserSettingsDisabled | 1 | 忽略 | 启用 |
| ComputerSettingsDisabled | 2 | 启用 | 忽略 |
| AllSettingsDisabled | 3 | 忽略 | 忽略 |
正常时客户端读到 Baseline19;ComputerSettingsDisabled 时 marker 消失,报告为 Not Applied (Unknown Reason);仅禁用用户分支时计算机 marker 恢复。重新启用并把内容改成 ReEnabled19 后,客户端收到新值,排除了只残留旧值的误判。
管理端检查应把状态、GPC flags、版本、扩展登记、ACL 和策略文件对应到同一 DC。原实验的诊断代码为:
$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue';
Import-Module ActiveDirectory,GroupPolicy; $dc='LAB-DC1.corp.test.checo.cc'; $domain='corp.test.checo.cc'; $root='DC=corp,DC=test,DC=checo,DC=cc';
$g=Get-GPO -Guid '7d35103b-a5e9-42ec-bb02-68184da5f13c' -Domain $domain -Server $dc
$g=Get-GPO -Guid $g.Id -Domain $domain -Server $dc
$a=Get-ADObject -Identity ("CN={"+$g.Id+"},CN=Policies,CN=System,$root") -Server $dc -Properties flags,versionNumber,gPCMachineExtensionNames,nTSecurityDescriptor
$ou=Get-ADObject -Identity '9bee8030-26d1-4790-a365-e4802f5b579e' -Server $dc -Properties gPLink
$p="\\$dc\SYSVOL\$domain\Policies\{"+$g.Id+"}\Machine\Registry.pol"
@{status=$g.GpoStatus.ToString();flags=$a.flags;version=$a.versionNumber;cse=$a.gPCMachineExtensionNames;sddl=$a.nTSecurityDescriptor.Sddl;link=$ou.gPLink;pol_hash=(Get-FileHash $p).Hash;wmi=if($g.WmiFilter){$g.WmiFilter.Name}else{$null}} | ConvertTo-Json -Depth 6这些 GUID 只标识原实验对象,复现时要换成当前目标。需要恢复分支时,先备份并记下原 GpoStatus,再用 GPMC 的“详细信息 → GPO 状态”或管理对象调整:
$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue';
Import-Module ActiveDirectory,GroupPolicy; $dc='LAB-DC1.corp.test.checo.cc'; $domain='corp.test.checo.cc'; $root='DC=corp,DC=test,DC=checo,DC=cc';
$g=Get-GPO -Guid '7d35103b-a5e9-42ec-bb02-68184da5f13c' -Domain $domain -Server $dc
$g.GpoStatus='AllSettingsEnabled'
$g=Get-GPO -Guid $g.Id -Domain $domain -Server $dc不要直接改 GPC flags 或 SYSVOL 文件。只需要计算机设置的业务 GPO 可以保留 UserSettingsDisabled,不必统一启用两个分支。回读状态后,仍在原客户端核对本轮实际值和事件列表。
6. 首选项目标定位
最后一组 GPP 在同一 GPO 放两个 Update 注册表项:无条件项 MarkerNoTarget=gpp-baseline,另一项要求 NetBIOS 计算机名等于 NOTLABMEMBER。实际客户端为 LAB-MEMBER。
故障阶段 GPO 在 Applied 中,扩展也正常完成,但只有无条件项写入。XML 中的条件为:
<Filters>
<FilterComputer bool="AND" not="0" type="NETBIOS" name="NOTLABMEMBER" />
</Filters>只把名称改成 LAB-MEMBER 后,两项都写入。正常跳过与执行错误不同;一个 AND 条件为 false 就足以让项目退出,检查时要展开完整的 AND/OR、取反和集合树。
| 阶段 | 无条件项 | 有条件项 | 扩展结果 |
|---|---|---|---|
| 名称不匹配 | gpp-baseline | 缺失 | Completed,234 ms |
| 名称匹配 | gpp-baseline | gpp-badtarget | Completed,62 ms |
两轮 4016/5016 的记录分别为 6659/6660 和 6697/6698。消息中的 Group Policy Registry Extension 指 GPP 注册表扩展,不能与另一条 Registry Extension 记录混读。扩展完成不证明每个项目都写入。
保留同一客户端、同一动作和路径,只修正需要的条件。GPO WMI 筛选控制整份策略;项目级目标只控制对应项目。不要为修复一个首选项而取消整份策略的安全筛选。
7. 验收与恢复
验收至少同时保存目标身份与 DN、所用 DC、GPO GUID/版本、RSoP、对应时间窗口的 Operational 事件,以及实际注册表/文件/服务结果。用户配置用该用户会话,计算机配置用计算机上下文;gpresult 里的名称和退出码不能替代业务状态。
上面各实验都保存了 GPO 备份和原链接配置。恢复只处理本次改动:ACL 按原权限回退,WMI 按导出的原定义及关联回退,GpoStatus 设回原值,链接逐条按 GUID 恢复。不要整片覆盖 OU 的旧 gPLink,以免丢掉他人的并行修改。
实验创建的专属对象则先撤链接、刷新,检查策略已退出作用域,再移回原计算机对象,按记录 GUID 删除测试 GPO、用户、组及空 OU。注册表策略 marker 在本次刷新后消失;GPP Update 值可能继续保留,测试前不存在的值应在确认策略退出后精确删除,原有业务值则恢复原类型、内容及存在状态。
各次结束记录都核对了计算机对象身份与原位置、安全通道、SYSVOL/NETLOGON,以及默认 GPO 的未改动状态。它们是各自实验的恢复证据,并不是对所有域健康检查的替代。
