<# .SYNOPSIS Forces a Group Policy refresh on the local device and reports whether it worked. .DESCRIPTION Checks that the device is domain-joined and still trusts the domain, then runs gpupdate /force (or a lighter changes-only refresh) for the target you choose, answering "No" to any logoff or reboot prompt so nothing gets bounced mid-afternoon. Returns an object with the exit code and the tail of gpupdate's output, and exits non-zero on failure so ConfigMgr reports it correctly. .PARAMETER Target Computer, User or Both. User policy only makes sense when the script runs as the signed-in user, not as SYSTEM. .PARAMETER WaitSeconds How long gpupdate waits for policy processing to finish before returning. .PARAMETER ChangedOnly Skip /force and apply only settings that changed. Much lighter on domain controllers when you're hitting a whole collection. .PARAMETER SkipSecureChannelCheck Don't test the computer's trust relationship with the domain first. .EXAMPLE .\Invoke-GroupPolicyRefresh.ps1 .EXAMPLE .\Invoke-GroupPolicyRefresh.ps1 -Target Both -WaitSeconds 300 -WhatIf #> [CmdletBinding(SupportsShouldProcess)] param( [ValidateSet('Computer', 'User', 'Both')] [string]$Target = 'Computer', [ValidateRange(0, 3600)] [int]$WaitSeconds = 120, [switch]$ChangedOnly, [switch]$SkipSecureChannelCheck ) $result = [pscustomobject]@{ ComputerName = $env:COMPUTERNAME Target = $Target DomainJoined = $null SecureChannel = $null ExitCode = $null Result = $null Output = $null } $system = Get-CimInstance -ClassName Win32_ComputerSystem $result.DomainJoined = [bool]$system.PartOfDomain if (-not $result.DomainJoined) { $result.Result = 'Skipped (not domain-joined)' return $result } if ($Target -ne 'Computer' -and [Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) { Write-Warning 'Running as SYSTEM, so the user half of this refresh applies to SYSTEM, not to whoever is signed in.' } # A broken trust relationship makes gpupdate fail in confusing ways. Check it up front. if (-not $SkipSecureChannelCheck -and (Get-Command -Name Test-ComputerSecureChannel -ErrorAction SilentlyContinue)) { try { $result.SecureChannel = Test-ComputerSecureChannel -ErrorAction Stop } catch { $result.SecureChannel = $false; Write-Verbose "Secure channel test failed: $($_.Exception.Message)" } if (-not $result.SecureChannel) { $result.Result = 'Failed (no secure channel to the domain)' Write-Error 'This computer cannot talk to a domain controller over its secure channel. Fix the trust relationship first.' $result exit 1 } } $arguments = @("/wait:$WaitSeconds") if (-not $ChangedOnly) { $arguments = @('/force') + $arguments } if ($Target -ne 'Both') { $arguments = @("/target:$($Target.ToLower())") + $arguments } if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, "gpupdate $($arguments -join ' ')")) { $result.Result = 'WhatIf' return $result } Write-Verbose "Running gpupdate $($arguments -join ' ')" # Pipe "N" so a policy that wants a logoff or reboot doesn't get one. $output = 'N' | & gpupdate.exe @arguments 2>&1 $result.ExitCode = $LASTEXITCODE $result.Output = (@($output | ForEach-Object { "$_".Trim() } | Where-Object { $_ }) | Select-Object -Last 4) -join ' | ' $result.Result = if ($result.ExitCode -eq 0) { 'Success' } else { 'Failed' } $result if ($result.ExitCode -ne 0) { exit $result.ExitCode }