When working with Group Policy in Windows environments, one of the most frequent pain points is determining the exact processing order of different policy components. While Microsoft's documentation outlines the basic hierarchy (Local → Site → Domain → OU), it lacks specificity regarding the execution sequence of individual policy elements like Software Installation, Startup Scripts, and Group Policy Preferences (GPP).
Through extensive testing in production environments (Windows Server 2016/2019 with Win10/Win11 clients), we've observed this execution pattern during computer policy processing:
1. Local Group Policy
2. Site-linked GPOs
3. Domain-linked GPOs
4. OU-linked GPOs (parent to child)
a. Security Settings
b. Group Policy Preferences (GPP)
c. Software Installation
d. Startup Scripts
5. Reboot-triggered processing cycles
Software installation via GPO occurs after GPP processing, which explains why immediate cleanup attempts fail. Here's a practical workaround using PowerShell in a startup script:
# Wait for software installation to complete
$maxRetries = 10
$retryInterval = 30 # seconds
function Test-SoftwareInstalled {
return Test-Path "C:\\Program Files\\WidgetA\\widget.exe"
}
$attempt = 0
while (-not (Test-SoftwareInstalled) -and $attempt -lt $maxRetries) {
Start-Sleep -Seconds $retryInterval
$attempt++
}
if (Test-SoftwareInstalled) {
# Now safe to apply patches and cleanup
Start-Process "\\\\domain\\sysvol\\patches\\widget_update.exe" -ArgumentList "/quiet" -Wait
Remove-Item "C:\\Users\\Public\\Desktop\\Unwanted Shortcut.lnk" -Force
}
Group Policy Preferences execute in this documented order within their category:
- Environment Variables
- Files
- Folders
- INI Files
- Registry
- Shortcuts
For complex deployment scenarios requiring precise coordination, consider this registry-based synchronization approach:
# In your GPP (Registry preference):
# Sets flag when software installation begins
Set-ItemProperty -Path "HKLM:\\SOFTWARE\\MyDeployment" -Name "InstallPhase" -Value 1
# In your startup script:
$phase = Get-ItemProperty -Path "HKLM:\\SOFTWARE\\MyDeployment" -Name "InstallPhase" -ErrorAction SilentlyContinue
if ($phase -eq 1) {
# Installation in progress, set completion flag
Set-ItemProperty -Path "HKLM:\\SOFTWARE\\MyDeployment" -Name "InstallPhase" -Value 2
Restart-Computer -Force
} elseif ($phase -eq 2) {
# Post-installation phase
ApplyUpdatesAndCleanup
Set-ItemProperty -Path "HKLM:\\SOFTWARE\\MyDeployment" -Name "InstallPhase" -Value 3
}
Group Policy processing follows a strict hierarchical order that many administrators gloss over:
1. Local Group Policy (LGPO)
2. Site-level policies
3. Domain-level policies
4. Organizational Unit policies (parent OU first)
5. Child OU policies (nested OUs processed from parent to child)
What most documentation misses is the internal processing order within Computer Configuration:
- Phase 1 - Core Settings: Security settings, PKI policies, and EFS recovery
- Phase 2 - Software Installation: MSI deployments (both assigned and published)
- Phase 3 - Script Execution: Startup scripts in defined order
- Phase 4 - Preferences: Group Policy Preferences (GPP) items
Here's how to handle your specific Widget A scenario with proper execution order:
# PowerShell script to verify software installation before running patch
$softwareCheck = Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -eq "Widget A"}
if ($softwareCheck -and (Test-Path "C:\\Program Files\\WidgetA\\widget.exe")) {
Start-Process -FilePath "\\\\server\\patches\\widget_update.exe" -ArgumentList "/silent /norestart" -Wait
}
Create a custom ADMX template to enforce dependencies:
<policyDefinition>
<policyNamespaces>
<target prefix="custom" namespace="Custom.Policies"/>
</policyNamespaces>
<resources minRequiredRevision="1.0"/>
<supportedOn>
<definitions>
<definition displayName="$(string.CustomOS)" name="Custom_OS"/>
</definitions>
</supportedOn>
<categories>
<category displayName="$(string.CustomSettings)" name="Custom_Settings"/>
</categories>
<policies>
<policy name="Custom_PostInstall" class="Machine" displayName="$(string.PostInstall)" explainText="$(string.PostInstallHelp)" presentation="$(presentation.PostInstall)" key="SOFTWARE\\Policies\\Custom" valueName="PostInstall">
<parentCategory ref="Custom_Settings"/>
<supportedOn ref="Custom_OS"/>
<enabledValue>
<decimal value="1"/>
</enabledValue>
<disabledValue>
<decimal value="0"/>
</disabledValue>
</policy>
</policies>
</policyDefinition>
Use this command to generate detailed logs:
gpresult /h gp_report.html /scope:computer
The key Event IDs to monitor in Event Viewer:
- Event ID 5016 - Software installation started
- Event ID 5017 - Software installation completed
- Event ID 5312 - Preferences application started