BW.Utils.GroupPolicy.psm1
<# .SYNOPSIS Increments the GPO version number causing the policy to refresh. .DESCRIPTION Increments the GPO version number causing the policy to refresh. You can increment either User, Computer, or Both version numbers. Handles updating AD and the GPT.INI file. If the file or AD update fails the function should revert the version number. The function should take pipeline input from Get-GPO. .PARAMETER Guid The GPO guid to increment .PARAMETER Name The GPO name to increment .PARAMETER Increment Choose to increment User, Computer, or Both .PARAMETER Domain See Get-GPO .PARAMETER Server See Get-GPO .EXAMPLE Get-GPO -Name 'Default Domain Policy' | Invoke-GPOIncrementVersion -Increment Computer Increments the computer policy version for the Default Domain Policy #> function Invoke-GPOIncrementVersion { [CmdletBinding( DefaultParameterSetName = 'Guid', SupportsShouldProcess, ConfirmImpact='Low' )] param( [Parameter( ParameterSetName = 'Guid', Mandatory, ValueFromPipelineByPropertyName, Position = 1 )] [Alias( 'Id' )] [guid[]] $Guid, [Parameter( ParameterSetName = 'Name', Mandatory )] [string[]] $Name, [Parameter( Mandatory )] [ValidateSet( 'Both', 'Computer', 'User' )] [string] $Increment, [string] $Domain, [string] $Server ) process { $PSBoundParameters.Remove( $PSCmdlet.ParameterSetName ) > $null if ( $PSBoundParameters.ContainsKey( 'Increment' ) ) { $PSBoundParameters.Remove( 'Increment' ) > $null } $GPOs = switch ( $PSCmdlet.ParameterSetName ) { 'Guid' { $Guid | ForEach-Object { Get-GPO -Guid $_ @PSBoundParameters } } 'Name' { $Name | ForEach-Object { Get-GPO -Name $_ @PSBoundParameters } } } $GPOs | ForEach-Object { # find the GPO in AD $GetGpoObjectSplat = @{ SearchBase = "CN=Policies,CN=System,$( $_.DomainName.Split('.').ForEach({"DC=$_"}) -join ',' )" Filter = "Name -eq '$( $_.Id.ToString('B') )' -and objectClass -eq 'groupPolicyContainer'" Server = $_.DomainName Properties = 'DisplayName', 'versionNumber', 'gPCFileSysPath' } $GpoObject = Get-ADObject @GetGpoObjectSplat # first calculate the existing version numbers # User Policy version is the left 16 bits # Computer Policy version is the right 16 bits $UserPolicyVersion = $GpoObject.versionNumber -shr 16 $ComputerPolicyVersion = $GpoObject.versionNumber -band 0xffff # increment only the requested version if ( $Increment -eq 'Both' -or $Increment -eq 'User' ) { $UserPolicyVersion += 1 } if ( $Increment -eq 'Both' -or $Increment -eq 'Computer' ) { $ComputerPolicyVersion += 1 } # move the user policy back to the left, then add the computer policy $NewVersion = ( $UserPolicyVersion -shl 16 ) + $ComputerPolicyVersion # actually apply the change if ( $PSCmdlet.ShouldProcess( "$($GpoObject.DisplayName) $($GpoObject.Name)", "increment the version number for $($Increment.ToLower())" ) ) { $ConfirmPreference = 'None' $GptIniPath = Join-Path $GpoObject.gPCFileSysPath 'GPT.INI' $BkpIniPath = Join-Path $GpoObject.gPCFileSysPath 'GPT.INI-BAK' if ( -not( Test-Path -Path $GptIniPath -PathType Leaf ) ) { Write-Error "Could not find GPO $($GpoObject.DisplayName) $($GpoObject.Name) in file system!" return } try { # make a backup Copy-Item -Path $GptIniPath -Destination $BkpIniPath -Force -ErrorAction Stop # update the version $IniContent = Get-Content -Path $GptIniPath $IniContent = $IniContent -replace 'Version=\d+', "Version=$NewVersion" $IniContent | Set-Content -Path $GptIniPath -Encoding Ascii -ErrorAction Stop } catch { Remove-Item -Path $BkpIniPath -ErrorAction SilentlyContinue Write-Error "Could not modify the GPO version for $($GpoObject.DisplayName) $($GpoObject.Name) in file system!" return } try { $GpoObject | Set-ADObject -Replace @{ versionNumber = $NewVersion } -ErrorAction Stop } catch { Copy-Item -Path $BkpIniPath -Destination $GptIniPath -Force | Out-Null Write-Error "Could not modify the GPO version for $($GpoObject.DisplayName) $($GpoObject.Name) in AD!" return } finally { Remove-Item -Path $BkpIniPath -ErrorAction SilentlyContinue } } } } } <# .SYNOPSIS Get GPO links in a domain .DESCRIPTION Bastardizes Set-GPLink to return Microsoft.GroupPolicy.GPLink objects for GPLinks in a domain. .EXAMPLE Get-GPLink -Name 'Default Domain Policy' .EXAMPLE Get-GPLink -Guid 31b2f340-016d-11d2-945f-00c04fb984f9 .EXAMPLE Get-GPO -Name 'Default Domain Policy' | Get-GPLink -Scope EntireForest #> function Get-GPLink { [CmdletBinding( PositionalBinding = $false )] param( [Parameter( ParameterSetName = 'Name', Mandatory )] [string] $Name, [Parameter( ParameterSetName = 'Guid', ValueFromPipelineByPropertyName, Mandatory )] [Alias( 'Id' )] [guid] $Guid, [string] $Target, [Parameter( ValueFromPipelineByPropertyName )] [Alias( 'DomainName' )] [string] $Domain, [string] $Server, [ValidateSet( 'Domain', 'AllSites', 'EntireForest' )] [string] $Scope = 'Domain' ) $RealScope = $Scope # force single scope if target is specified if ( $Target ) { $RealScope = 'Single' } $PSBoundParameters.Remove( 'Target' ) > $null $PSBoundParameters.Remove( 'Scope' ) > $null $PSBoundParameters.Remove( 'Verbose' ) > $null $GPO = Get-GPO @PSBoundParameters if ( -not $GPO ) { return } Write-Verbose "Found GPO: $($GPO.DisplayName)" $PSBoundParameters.Remove( 'Name' ) > $null $PSBoundParameters.Guid = $GPO.Id $PSBoundParameters.Domain = $GPO.DomainName if ( $Server ) { $DomainDC = Get-ADDomainController -Identity $Server -Server $Server } else { Write-Verbose "Searching for domain controller for domain $Domain..." $DomainDC = Get-ADDomainController -DomainName $PSBoundParameters.Domain -Discover $Server = $DomainDC | Select-Object -ExpandProperty HostName -First 1 } if ( -not $DomainDC ) { return } if ( $Scope -eq 'Domain' -or $DomainDC.IsGlobalCatalog ) { Write-Verbose "Using domain controller $Server." } else { $Server = Get-ADDomainController -DomainName $DomainDC.Forest -Discover -Service GlobalCatalog | Select-Object -ExpandProperty HostName -First 1 Write-Warning "Selected server is not a global catalog, using $Server instead." } $SearchSplat = @{ Filter = "gplink -like '*{0}*'" -f ([guid]$GPO.Id).ToString('B') Properties = 'DistinguishedName' Server = $Server + ':3268' } switch ( $RealScope ) { 'Single' { $SearchSplat.SearchBase = $Target $SearchSplat.SearchScope = 'Base' } 'Domain' { $SearchSplat.SearchBase = $Domain.Split('.').ForEach({ "DC=$_" }) -join ',' $SearchSplat.Server = $Server } 'AllSites' { $SearchSplat.SearchBase = "CN=Sites," + (Get-ADRootDSE -Server $Server).configurationNamingContext $SearchSplat.SearchScope = 'OneLevel' } 'EntireForest' { $SearchSplat.SearchBase = (Get-ADRootDSE -Server $Server).rootDomainNamingContext } } $SearchSplat.Keys | ForEach-Object { Write-Verbose ( '{0,-14} : {1}' -f $_, $SearchSplat[$_] ) } Get-ADObject @SearchSplat | ForEach-Object { Set-GPLink -Target $_.DistinguishedName @PSBoundParameters } } <# .SYNOPSIS Import Group Policy backups manifest .DESCRIPTION Import Group Policy backups manifest into PSCustomObjects .PARAMETER BackupPath Path to a Group Policy backup repository .EXAMPLE Get-Item ~\PolicyBackups | Import-GPBackupManifest #> function Import-GPBackupManifest { param( [Parameter( Mandatory, ValueFromPipelineByPropertyName, Position = 1 )] [Alias( 'FullName', 'Path' )] [string] $BackupPath ) # user provided path to backup manifest, not backup folder if ( [System.IO.Path]::GetFileName( $BackupPath ) -eq 'manifest.xml' ) { $BackupPath = [System.IO.Path]::GetDirectoryName( $BackupPath ) } # build the manifest path $ManifestPath = Join-Path $BackupPath 'manifest.xml' # check for a manifest if ( -not( Test-Path $ManifestPath ) ) { Write-Error 'Path must point to a Group Policy backup repository.' return } # load the manifest xml [xml]$Manifest = Get-Content -Path $ManifestPath # build PSCustomObjects from the xml foreach ( $BackupInst in $Manifest.Backups.BackupInst ) { $Object = @{} ( $BackupInst | Get-Member -MemberType Property ).Name.ForEach({ $Object[$_] = $BackupInst.($_).'#cdata-section' }) # include the path to the backup in the object $Object['Path'] = $BackupPath [pscustomobject]$Object } } <# .SYNOPSIS Locate a GPRegistryValue in a domain .DESCRIPTION Search all Group Policies for a GPRegistryValue in a domain .PARAMETER Key The registry key to search for .PARAMETER ValueName The ValueName(s) to search for .EXAMPLE Find-GPRegistryValue -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient' -ValueName 'SearchList' Will find any GPO where the DNS suffix search list is set. #> function Find-GPRegistryValue { [CmdletBinding()] param( [Parameter( Mandatory = $true )] [Alias( 'FullKeyPath' )] [string] $Key, [Parameter( Mandatory = $true )] [SupportsWildcards()] [string[]] $ValueName, [Parameter( ValueFromPipelineByPropertyName = $true )] [Alias( 'DomainName' )] [string] $Domain, [Alias( 'DC' )] [string] $Server ) $GpoSplat = @{} if ( $Domain ) { $GpoSplat.Domain = $Domain } if ( $Server ) { $GpoSplat.Server = $Server } Get-GPO -All @GpoSplat | ForEach-Object { $GpoChanged = $false $GpoObject = $_ | Select-Object *, @{N='Settings'; E={ ,[System.Collections.ArrayList]::new() }} Write-Verbose "Searching GPO '$($GpoObject.DisplayName)' ($($GpoObject.Id))" Search-GPRegistryValues -Guid $_.Id @PSBoundParameters | ForEach-Object { Write-Verbose " $($_.ValueName): $($_.Value)" $GpoObject.Settings.Add( $_ ) > $null $GpoChanged = $true } if ( $GpoChanged ) { return $GpoObject } } } <# .SYNOPSIS Find all GPRegistryValues in a given GPO .DESCRIPTION Find all GPRegistryValues in a given GPO, optionally filtering by name .PARAMETER Key The registry key to search for .PARAMETER ValueName The ValueName(s) to search for .EXAMPLE Search-GPRegistryValues -Key 'HKEY_LOCAL_MACHINE\SOFTWARE' Will list all GPRegistryValues in the GPO .EXAMPLE Search-GPRegistryValues -Key 'HKEY_LOCAL_MACHINE\SOFTWARE' -ValueName '*dns*' Will list all GPRegistryValues with a name matching *dns*. #> function Search-GPRegistryValues { [CmdletBinding()] param( [Parameter( Mandatory = $true )] [Alias( 'FullKeyPath' )] [string] $Key, [SupportsWildcards()] [string[]] $ValueName = '*', [Parameter( ParameterSetName = 'GetByName', Mandatory = $true, ValueFromPipeline = $true )] [Alias( 'DisplayName' )] [string] $Name, [Parameter( ParameterSetName = 'GetByGUID', Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [Alias( 'Id' )] [guid] $Guid, [Parameter( ValueFromPipelineByPropertyName = $true )] [Alias( 'DomainName' )] [string] $Domain, [Alias( 'DC' )] [string] $Server ) # remove Key from $PSBoundParameters for splatting $PSBoundParameters.Remove( 'Key' ) > $null $PSBoundParameters.Remove( 'ValueName' ) > $null # turn PSProvider path into PSPath if ( $Key -match '^(?<HiveName>\w+:)\\(?<RegistryKey>.*)$' ) { $Key = (Get-Item $Matches.HiveName -ErrorAction Stop).PSPath + $Matches.RegistryKey } # remove PSPath prefix $Key = $Key -replace '^Microsoft.PowerShell.Core\\Registry::' Get-GPRegistryValue -Key $Key @PSBoundParameters -PipelineVariable ValueItem -ErrorAction SilentlyContinue | ForEach-Object { if ( $ValueItem.HasValue ) { if ( $ValueName | Where-Object { $ValueItem.ValueName -like $_ } ) { $ValueItem } } else { Search-GPRegistryValues -Key $ValueItem.FullKeyPath -ValueName $ValueName @PSBoundParameters } } } |