<# Microsoft rolled out security update MS16-072 that changes the behaviour of Group Policy processing to address a security vulnerability. This script will allow you to audit your current state and address this by adding either the Authenticated Users or Domain Computers group the required Read permissions. The official response from Microsoft: - Deploying Group Policy Security Update MS16-072 \ KB3163622 https://blogs.technet.microsoft.com/askds/2016/06/22/deploying-group-policy-security-update-ms16-072-kb3163622/ The original script was released by Darren Mar-Elia as part of a blog article: - Group Policy Patch MS16-072– "Breaks" GP Processing Behavior https://sdmsoftware.com/group-policy-blog/bugs/new-group-policy-patch-ms16-072-breaks-gp-processing-behavior/ Have also taken ideas from Ian Farr at Microsoft in the following blog article and script: - MS16-072 – Known Issue – Use PowerShell to Check GPOs https://blogs.technet.microsoft.com/poshchap/2016/06/16/ms16-072-known-issue-use-powershell-to-check-gpos/ Jeremy Moskowitz also put some information together in a blog article: - Never a dull moment with Group Policy (or what to do about MS16-072) http://www.gpanswers.com/never-a-dull-moment-with-group-policy-or-what-to-do-about-ms16-072/ The script has been modified to allow for: - It defaults to report only mode so that you can: a) Determine how wide spread the issue may be. b) List the GPOs that need to be changed for the Change Management purposes. - It will write to a CSV file that can be openned with Excel. - Feedback from Erik de Vries via Darren's article so that it works across different OSs. - Runs on non-English systems as commented by Darren for Domain Computers. - As well as the CSV output, it also writes to a transcription log. - Detects the version of Windows you are using so it calls right cmdlets: - Get-GPPermission v Get-GPPermissions - Set-GPPermission v Set-GPPermissions - It gives you the option of applying either Domain Computers or Authenticated Users with Read permissions; depending on your strategy. Domain Computers being the preferred option and is what the script will default to. - Requires a minimum of PowerShell 2.0 - Feedback from Jean-Pierre Paradis to include Authenticated Users for non-English systems. - Improved screen output at start and finish to remove confusion. - Process against multiple domains as specified by the -Domains parameter. - Feedback from Trevor Bruss to fix an issue with the multiple domain environment. Syntax examples: - To execute the script in report only mode and process only the GPOs that have user settings: AddGPOReadPerms.ps1 - To execute the script in report only mode against multiple domains and process only the GPOs that have user settings: AddGPOReadPerms.ps1 -Domains:"corp.tailspintoys.com,corp.contoso.com,staff.adventure-works.com" - To execute the script in report only mode against all GPOs: AddGPOReadPerms.ps1 -All - To execute the script and take action against GPOs that have user settings adding Domain Computers with Read permissions: AddGPOReadPerms.ps1 -Action - To execute the script and take action against GPOs that have user settings adding Authenticated Users with Read permissions instead of Domain Computers: AddGPOReadPerms.ps1 -Action -AuthUsers - To execute the script and take action against all GPOs adding Domain Computers with Read permissions: AddGPOReadPerms.ps1 -Action -All - To execute the script and take action against all GPOs adding Authenticated Users with Read permissions instead of Domain Computers: AddGPOReadPerms.ps1 -Action -All -AuthUsers Script name: AddGPOReadPerms.ps1 Release 1.8 Written by Darren Mar-Elia (darren@sdmsoftware.com) 15th June 2016 Modified by Jeremy Saunders (jeremy@jhouseconsulting.com) 30th July 2016 #> #------------------------------------------------------------- param( [string]$Domains="", [switch]$All, [switch]$Action, [switch]$AuthUsers ) # Set Powershell Compatibility Mode Set-StrictMode -Version 2.0 #------------------------------------------------------------- # Use PowerShell to Find Operating System Version # You can use "[System.Environment]::OSVersion.Version", or the PInvoke method as per the following Scripting Guy article: # http://blogs.technet.com/b/heyscriptingguy/archive/2014/04/25/use-powershell-to-find-operating-system-version.aspx Function Get-OSVersion { $signature = @" [DllImport("kernel32.dll")] public static extern uint GetVersion(); "@ Add-Type -MemberDefinition $signature -Name "Win32OSVersion" -Namespace Win32Functions -PassThru } $os = [System.BitConverter]::GetBytes((Get-OSVersion)::GetVersion()) $majorVersion = $os[0] $minorVersion = $os[1] $build = [byte]$os[2],[byte]$os[3] $buildNumber = [System.BitConverter]::ToInt16($build,0) [float]$OSVersion = $majorVersion.ToString() + "." + $minorVersion.ToString() #------------------------------------------------------------- $invalidChars = [io.path]::GetInvalidFileNamechars() $datestampforfilename = ((Get-Date -format s).ToString() -replace "[$invalidChars]","-") # Get the script path $ScriptPath = {Split-Path $MyInvocation.ScriptName} $ScriptName = [System.IO.Path]::GetFilenameWithoutExtension($MyInvocation.MyCommand.Path.ToString()) $Logfile = "$ScriptName-$($datestampforfilename).log" $logPath = $(&$ScriptPath) try { Start-Transcript "$logPath\$logFile" } catch { Write-Verbose "This host does not support transcription" -Verbose } #------------------------------------------------------------- # Import the Active Directory Module Import-Module ActiveDirectory -WarningAction SilentlyContinue # Import the Group Policy Module Import-Module GroupPolicy -WarningAction SilentlyContinue #------------------------------------------------------------- $DomainsToProcess = @() if ([String]::IsNullOrEmpty($Domains) -OR $Domains.Trim() -eq "") { $DomainsToProcess += (get-addomain).DNSroot } Else { $DomainsToProcess += $Domains.split(" ") } Write-Verbose "Processing $(($DomainsToProcess | Measure-Object).Count) Domains in total." -Verbose ForEach ($Domain in $DomainsToProcess) { $GPOsProcessed = 0 $GPOsNoAuthUser = 0 Try { # Get the Domain SID. $DomainSID = (Get-ADDomain $Domain).DomainSID # Get the Domain NetBIOS name. $DomainNetBIOS = (Get-ADDomain $Domain).NetBIOSName $IsValidDomain = $True } Catch { $IsValidDomain = $False } If ($IsValidDomain) { # Get the Domain Computers group support for non-English systems. $DomainComputersName = $DomainNetBIOS + "\" + (Get-ADGroup -server $Domain "$($DomainSID)-515").Name # Get the Authenticated Users group support for non-English systems. $AuthUserName = (([System.Security.Principal.SecurityIdentifier]"S-1-5-11").Translate([System.Security.Principal.NTAccount]).Value) $GPOs = Get-GPO $Domain -all $Count = ($GPOs | Measure-Object).Count If ($Count -gt 0) { write-host " " Write-Verbose "There are $Count GPOs to process in the $Domain domain." -Verbose If ($All) { Write-Verbose "- Processing all GPOs." -Verbose } Else { Write-Verbose "- Processing GPOs with user settings only." -Verbose } write-host " " foreach ($gpo in $GPOs) { $ProcessGPO = $False if ($All) { $ProcessGPO = $True } if ($All -eq $False -AND $gpo.user.DSVersion -gt 0) { $ProcessGPO = $True } if ($ProcessGPO) { $AuthUser = $null $DomComp = $null $obj = New-Object -TypeName PSObject $obj | Add-Member -MemberType NoteProperty -Name "Domain" -value $Domain $obj | Add-Member -MemberType NoteProperty -Name "DisplayName" -value $gpo.displayname $obj | Add-Member -MemberType NoteProperty -Name "Name" -value $gpo.id # Read the GPO permissions to find out if Authenticated Users and Domain Computers is missing. if ($OSVersion -ge 6.2) { $AuthUser = Get-GPPermission -Domain $Domain -Guid $gpo.id -TargetName $AuthUserName -TargetType group -ErrorAction SilentlyContinue $DomComp = Get-GPPermission -Domain $Domain -Guid $gpo.id -TargetName $DomainComputersName -TargetType group -ErrorAction SilentlyContinue } Else { $AuthUser = Get-GPPermissions -Domain $Domain -Guid $gpo.id -TargetName $AuthUserName -TargetType group -ErrorAction SilentlyContinue $DomComp = Get-GPPermissions -Domain $Domain -Guid $gpo.id -TargetName $DomainComputersName -TargetType group -ErrorAction SilentlyContinue } If ($AuthUser -eq $null) { # Authenticated Users has been removed $AuthUserRead = $False $CustomAuthUserPerm = $False } Else { If (($AuthUser.Permission -eq "GpoApply") -OR ($AuthUser.Permission -eq "GpoRead")) { # Authenticated Users has Read permissions $AuthUserRead = $True $CustomAuthUserPerm = $False } Else { # Authenticated Users does not have Read Permissions but Custom Permissions have been set $AuthUserRead = $False $CustomAuthUserPerm = $True } } if ($DomComp -eq $null) { # Domain Computers do not have direct permissions $DomainComputersRead = $False } Else { If (($DomComp.Permission -eq "GpoApply") -OR ($DomComp.Permission -eq "GpoRead")) { # Domain Computers has Read permissions $DomainComputersRead = $True } Else { $DomainComputersRead = $False } } $obj | Add-Member -MemberType NoteProperty -Name "AuthUserRead" -value $AuthUserRead $obj | Add-Member -MemberType NoteProperty -Name "CustomAuthUserPerm" -value $CustomAuthUserPerm $obj | Add-Member -MemberType NoteProperty -Name "DomainComputersRead" -value $DomainComputersRead If ($AuthUserRead -eq $False -AND $DomainComputersRead -eq $False) { $GPOsNoAuthUser++ $ToBeFixed = $True Write-Warning "$($gpo.DisplayName)" -Verbose } Else { $ToBeFixed = $False Write-Verbose "$($gpo.DisplayName)" -Verbose } If ($ToBeFixed -AND $Action) { If ($AuthUsers -eq $False) { $TargetName = $DomainComputersName } Else { $TargetName = $AuthUserName } if ($OSVersion -ge 6.2) { Set-GPPermission -Domain $Domain -Guid $gpo.Id -PermissionLevel GpoRead -TargetName $TargetName -TargetType Group } Else { Set-GPPermissions -Domain $Domain -Guid $gpo.Id -PermissionLevel GpoRead -TargetName $TargetName -TargetType Group } } # Set the output file path and name. $OutputFile = $(&$ScriptPath) + "\$ScriptName-$Domain-$($datestampforfilename).csv" # PowerShell V2 doesn't have an Append parameter for the Export-Csv cmdlet. Out-File does, but it's # very difficult to get the formatting right, especially if you want to use quotes around each item # and add a delimeter. However, we can easily do this by piping the object using the ConvertTo-Csv, # Select-Object and Out-File cmdlets instead. if ($PSVersionTable.PSVersion.Major -gt 2) { $obj | Export-Csv -Path "$OutputFile" -Append -Delimiter "," -NoTypeInformation -Encoding ASCII } Else { if (!(Test-Path -path $OutputFile)) { $obj | ConvertTo-Csv -NoTypeInformation -Delimiter "," | Select-Object -First 1 | Out-File -Encoding ascii -filepath "$OutputFile" } $obj | ConvertTo-Csv -NoTypeInformation -Delimiter "," | Select-Object -Skip 1 | Out-File -Encoding ascii -filepath "$OutputFile" -append -noclobber } $obj = $null $GPOsProcessed ++ } } write-host " " Write-Verbose "Summary for the $Domain domain:" -Verbose If ($All) { Write-Verbose "- All $Count GPOs were processed." -Verbose } Else { Write-Verbose "- Only $GPOsProcessed GPOs with user settings were processed." -Verbose } If ($GPOsNoAuthUser -gt 0 -AND $Action) { Write-Verbose "- $GPOsNoAuthUser out of $Count GPOs that have been modified by granting $TargetName Read permissions." -Verbose } Else { If ($GPOsNoAuthUser -gt 0) { Write-Warning "- $GPOsNoAuthUser out of $Count GPOs that need to be modified by granting either $DomainComputersName or $AuthUserName Read permissions." -Verbose } Else { Write-Verbose "- No GPOs require modification." -Verbose } } write-host " " } } Else { Write-Warning "The $Domain domain could not be contacted." -Verbose write-host " " } } #------------------------------------------------------------- try { Stop-Transcript } catch { Write-Verbose "This host does not support transcription" }