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

#>

function Get-GPLink {

    [CmdletBinding(
        PositionalBinding = $false
    )]
    param(
    
        [Parameter(
            ParameterSetName = 'Guid',
            Mandatory
        )]
        [guid[]]
        $Guid,

        [Parameter(
            ParameterSetName = 'Name',
            Mandatory
        )]
        [string[]]
        $Name,

        [string]
        $Target,

        [string]
        $Domain = $env:USERDNSDOMAIN,

        [string]
        $Server

    )

    process {

        $Server = Get-ADDomainController -DomainName $Domain -Discover |
            Select-Object -ExpandProperty HostName -First 1

        $RootDSE = Get-ADRootDSE -Server $Server

        $GPSplat = @{}
        if ( $Domain ) { $GPSplat.Domain = $Domain }
        if ( $Server ) { $GPSplat.Server = $Server }

        if ( $Name ) {

            $Guid = $Name.ForEach({ Get-GPO -Name $_ @GPSplat }).Id

        }

        foreach ( $CurrentGuid in $Guid ) {

            $SearchSplat = @{
                Server = $Server
                Filter = "gplink -like '*$CurrentGuid*'"
                Properties = 'DistinguishedName'
            }

            [System.Collections.Generic.List[string]]$Result = @()

            # handle specific searches
            if ( $Target ) {

                $SearchSplat.SearchBase = $Target
                $SearchSplat.SearchScope = 'Base'

                Get-ADObject @SearchSplat |
                    ForEach-Object { $Result.Add( $_.DistinguishedName ) }
            
            }
            
            # handle generic searches
            else {

                Get-ADObject -SearchBase $RootDSE.defaultNamingContext @SearchSplat |
                    ForEach-Object { $Result.Add( $_.DistinguishedName ) }
                
                Get-ADObject -SearchBase $RootDSE.configurationNamingContext @SearchSplat |
                    ForEach-Object { $Result.Add( $_.DistinguishedName ) }
            
            }

            # use Set-GPLink to get a GPLink object reference
            foreach ( $DistinguishedName in $Result ) {

                Set-GPLink -Guid $CurrentGuid -Target $DistinguishedName @GPSplat
                
            }
        
        }
    
    }

}