#Update-GPOLinks.ps1 #Alan dot Kaplan at va dot gov #2/17/15 initial version 1 #3/27/15 v 1.1 Changed titles, tried to fix error message and success message printing when error occurs. #10/13/16 v 2 Added support of attributes linked, enforced, link order, test for domain admin membership #requires -version 3 #requires -module activedirectory #requires -module GroupPolicy Import-module grouppolicy, activedirectory Add-Type -assemblyname Microsoft.visualBasic Function EchoAndLog { param([string] $strText) #Echo to host and append to file Tee-Object -Append -FilePath $logfile -inputobject $strText } Function Bool2String($bVar){ if ($bVar -eq $true) {'Yes'}ELSE {'No'} } Function Get-AllGPOLinks{ #Requires -module GroupPolicy #Requires -module ActiveDirectory #Requires -version 3 <# .Synopsis Get all the GPO links for a specified domain .DESCRIPTION This script gets all the GPO links for a specified domain .EXAMPLE $domain = 'contosco.com' $GPOName = 'My GPO Settings' Get-AllGPOLinks $domain | where {$_.gpname -eq $GPOName} .Notes Alan Kaplan 10-5-2016 This is a version of the script found here: http://techibee.com/group-policies/find-link-status-and-enforcement-status-of-group-policies-using-powershell/2424 My version collects all GPO links, allows you to specify domain, adds error handling, and converted output to PSCustomObject, added comments about closing brackets, added advanced function and #Required #> [CmdletBinding()] Param ( # dns domain Name [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] $Domain ) $OUs =Get-ADOrganizationalUnit -Filter * -Properties GPLink -server $domain $AllLinks = foreach($OU in $OUs) { $OUName = $OU.Name $OUDN = $OU.DistinguishedName #Hacky way to get LDAP strings. Regex might be best option here if ($OU.GPLink){ $OUGPLinks = $OU.GPLink.split("][") #Get rid of all empty entries the array $OUGPLinks = @($OUGPLinks | ? {$_}) if ($OUGPLinks.Length -gt 1) { $order = $OUGPLinks.Count foreach($GPLink in $OUGPLinks) { $error.Clear() Try{ $objGPO = [adsi]$GPLink.Split(";")[0] $GpName = $objGPO | select -expandProperty displayName $GpStatus = $GPLink.split(";")[1] $EnableStatus = $EnforceStatus = 0 switch($GPStatus) { "1" {$EnableStatus = $false; $EnforceStatus = $false} "2" {$EnableStatus = $true; $EnforceStatus = $true} "3" {$EnableStatus = $false; $EnforceStatus = $true} "0" {$EnableStatus = $true; $EnforceStatus = $false} } [PSCustomobject]@{ OUName = $OUName OUDN = $OUDN GPName = $GPName IsLinked = $EnableStatus IsEnforced = $EnforceStatus GPOrder = $Order } #end PSCustomObject }Catch{ Write-warning $error[0].exception.message Get-GPO -Domain $Domain -Guid } $order -- } #End foreach GPLink } #End if $OUGPLinks.Length } #End if $OU.GPLink } $AllLinks | sort OUDN, GPOrder } Function Get-AllSecurityGroupsForUser{ <# .Synopsis Get all security groups that a user belongs to, directly and indirectly .DESCRIPTION This script uses ADSI to get all of the groups that a user is a member of. The forest is searrched for the user's SamAccountName .PARAMETER Name The SamAccountName for the user. Because the entire forest is searched, the domain should be omitted .EXAMPLE Get-AllGroupsForUser $env:UserName | Out-GridView .Notes The interesting bits are from http://abhishek225.spaces.live.com/, Function to list the Security Groups a User is Member Of Kaplan switched to DN for input, added handling for SIDs in output also added code to search for name in forest from comments below https://blogs.technet.microsoft.com/heyscriptingguy/2010/10/12/use-powershell-to-translate-a-users-sid-to-an-active-directory-account-name/ #> [CmdletBinding()] Param ( [Parameter( Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0, HelpMessage="Enter SamAccount for user")] [string]$Name ) Begin{ $forest=[system.directoryservices.activedirectory.Forest]::GetCurrentForest().Name } Process { $SAMName = $Name If ($name.Contains("@")){ Write-Warning "$Name is not a name in the NT format, quitting" Break } if ($name.Contains("\")){ $a = ($name).Split("\") $SAMName = $a[1] } $searcher=[adsisearcher]"samaccountname=$SAMName" $searcher.SearchRoot="GC://$Forest" $searcher.PropertiesToLoad.Add('distinguishedname') | Out-Null $results=$searcher.FindOne() $DistinguishedName = $($results.Properties.distinguishedname).ToString() # assumes you have the right permissions, otherwise use new-object and pass creds $user = [adsi]"LDAP://$DistinguishedName" # Load the TokenGroups attribute in the Property Cache $user.psbase.refreshCache(@("TokenGroups")) # Convert the SID to to NT Account $irc = new-object System.Security.Principal.IdentityReferenceCollection foreach($sidByte in $user.TokenGroups) { $irc.Add((new-object System.Security.Principal.SecurityIdentifier $sidByte,0)) } ($irc.Translate([System.Security.Principal.NTAccount]) | foreach { #Kaplan - takes care of some built-in groups that don't work with name-translate if ($_.isAccountSid){ $bind = "LDAP://" ([adsi]$Bind).name }ELSE{$_} }).GetEnumerator().value | sort } } Function Main{ $FindGPO = $List |Out-GridView -OutputMode Single -Title "Select the Current GPO to Find and click OK" if ($FindGPO -eq $null) {Break} $FindGPOName = ($FindGPO).DisplayName.ToString() $ReplaceGPO = $List |Out-GridView -OutputMode Single -Title "Select the New GPO to Replace it with and click OK" if ($ReplaceGPO -eq $null) {Break} $ReplaceGPOName = ($ReplaceGPO).DisplayName.ToString() #If log is new, add header if ((Test-Path $logfile) -eq $False) { $header = "Update GPO-Links Log. $env:UserName on " + (Get-Date).ToString() if ($bTest){$header += "`n******* Test Mode, no changes made *******`n"} Out-File -FilePath $logfile -inputobject $header } $GPOInfo=Get-GPOReport -domain $domain -server $domain -Name $FindGPOName -ReportType xml $msg = "Checking all OUs in $Domain to create a list of existing links" Write $msg $AllDNlinks = Get-AllGPOLinks $domain $DNlinks = $AllDNlinks| Where {$_.GPName -eq $FindGPOName} if ($DNlinks.count -eq 0){ Write-Warning "No links found for $FindGPOName" Exit } $error.Clear() Write "`n`nDone. Have the list of links, unlinking $FindGPOName`n" #Unlink Old $DNlinks | Foreach { $msg ="" $OUDN = [string]($_).OUDN #$iLinkOrder = ($_).GPOrder #$bEnforce = Bool2String ($_).isEnforced #$LinkEnabled = Bool2String ($_).isLinked Remove-GPLink -Domain $domain -Target $OUDN -Name $FindGPOName -WhatIf:$bTest -ErrorAction SilentlyContinue if ($error){ $Msg = "`nFailed to remove $FindGPOName GPO in the $domain domain from the Active Directory container with the LDAP path $OUDN. " +` $error[0].Exception.Message $error.Clear() }Else{ $Msg = "`nRemoved the link from for the $FindGPOName GPO in the $domain domain to the Active Directory container with the LDAP path $OUDN." } EchoAndLog $Msg } Write "Now linking $ReplaceGPOName`n" $DNlinks | Foreach { $OUDN = [string]($_).OUDN $iLinkOrder = ($_).GPOrder $bEnforce = Bool2String ($_).isEnforced $LinkEnabled = Bool2String ($_).isLinked New-GPLink -domain $domain -Target $OUDN -Name $ReplaceGPOName -LinkEnabled $LinkEnabled -Order $iLinkOrder -Enforced $bEnforce -WhatIf:$bTest -ErrorAction SilentlyContinue if ($error){ $Msg = "`nFailed to create a link for $ReplaceGPOName GPO in the $domain domain to the Active Directory container with the LDAP path " + $OUDN.tostring() + ". LinkOrder is $iLinkOrder, Enforced is $bEnforce, LinkEnabled is $LinkEnabled`. " +` $error[0].Exception.Message $error.Clear() }Else{ $Msg = "`nCreated a link from for the $ReplaceGPOName GPO in the $domain domain to the Active Directory container with the LDAP path "+ $OUDN.tostring() + ". LinkOrder is $iLinkOrder, Enforced is $bEnforce, LinkEnabled is $LinkEnabled`. " } EchoAndLog $Msg } } #=========== Script Begins =============== $MySecGroups = Get-AllSecurityGroupsForUser $env:userName if (![regex]::IsMatch($MySecGroups, 'Domain Admins')){ Write-Warning "Quitting. To ensure all GPOs are processed, please rerun this script as a Domain Admin" Exit } $msg =@" This script allows you to replace links to a current GPO with links to a newer version. It assumes that you have sufficient permissions, and that both new and old GPOs are present. Select GPOs in what domain? "@ $Script:domain = [Microsoft.VisualBasic.Interaction]::InputBox($msg, "Domain", "$env:userdnsdomain") if ($domain.Length -eq 0) {Exit} $domain = $domain.ToUpper() $msg = @" 1) Commit AD Changes 2) Run in Test Mode 0) Quit "@ $retval= [Microsoft.VisualBasic.Interaction]::InputBox($msg,"Continue with Group Policy Search Replace",2) Switch( $retval ){ 1 {$bTest = $False ; Break} 2 {$bTest = $True ;Break} default {Exit} } #Define default log $logFile = $env:userprofile + "\desktop\"+$Domain+"_GPOUpdateLog.txt" #prompt for logfile $Logfile = [Microsoft.VisualBasic.Interaction]::InputBox("Path to Log", "Logfile", "$logfile") if ($Logfile.Length -eq 0) {Exit} Write "Reading the list of all group policy objects in $domain. Please wait ...." $List = Get-GPO -All -server $domain -Domain $domain | Select-Object -Property DisplayName,Owner, GPOStatus,ID | Sort DisplayName $retval = "Yes" do { Main $msg="Do another within $script:domain"+'?' $retval = [Microsoft.VisualBasic.Interaction]::MsgBox($msg,'YesNo,defaultbutton2,Question', "Respond please") } until ($retval -eq "No") Write "Done.`n"