BW.Utils.GroupPolicy.WMIFilter.psm1

using namespace System.DirectoryServices.ActiveDirectory
using namespace System.DirectoryServices
using namespace System.Security.Principal
using namespace System.Collections


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function New-WmiFilterObject {

    [OutputType([WmiFilterObject])]
    param (

        [Parameter(Mandatory, Position=1)]
        [ValidatePattern('^SELECT.*FROM.*WHERE.*$')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Filter,

        [ValidateNotNullOrEmpty()]
        [string]
        $NameSpace = 'root\CIMv2',

        [ValidateSet('WQL')]
        [string]
        $Language = 'WQL',

        [WmiFilterList]
        $WmiFilterList

    )

    $WmiFilterObject = [WmiFilterObject]@{
            Filter      = $Filter
            NameSpace   = $NameSpace
            Language    = $Language
    }

    if ( $WmiFilterList -is [WmiFilterList] ) {

        $WmiFilterList.Add( $WmiFilterObject ) > $null

    } else {
        
        return $WmiFilterObject

    }

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function New-WmiFilterList {

    [CmdletBinding(DefaultParameterSetName='EmptyList')]
    [OutputType([WmiFilterList])]
    param (

        [Parameter(ParameterSetName='FromObject', Mandatory, Position=1)]
        [WmiFilterObject[]]
        $WmiFilterObject,
        
        [Parameter(ParameterSetName='FromString', Mandatory, Position=1)]
        [ValidatePattern('^SELECT.*FROM.*WHERE.*$')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Filter,

        [Parameter(ParameterSetName='FromString')]
        [ValidateNotNullOrEmpty()]
        [string]
        $NameSpace = 'root\CIMv2',

        [Parameter(ParameterSetName='FromString')]
        [ValidateSet('WQL')]
        [string]
        $Language = 'WQL'

    )

    $WmiFilterList = [WmiFilterList]::new()

    switch ( $PSCmdlet.ParameterSetName ) {

        'FromObject' {

            $WmiFilterList.AddRange( $WmiFilterObject ) > $null

        }

        'FromString' {

            $Filter | ForEach-Object {

                $WmiFilterList.Add([WmiFilterObject]@{
                    Filter      = $Filter
                    NameSpace   = $NameSpace
                    Language    = $Language
                }) > $null
    
            }

        }

    }

    # note that the comma is important so PowerShell doesn't convert to an Array
    return , $WmiFilterList

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Get-GPWmiFilter {

    [CmdletBinding()]
    param(
    
        [Parameter(
            ParameterSetName = 'GetAll',
            Mandatory
        )]
        [switch]
        $All,

        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory,
            Position = 0,
            ValueFromPipeline
        )]
        [Alias( 'DisplayName' )]
        [string]
        $Name,

        [Parameter(
            ParameterSetName = 'ByGUID',
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [guid]
        $Guid,

        [Parameter(
            ParameterSetName = 'ByGPO',
            Mandatory,
            Position = 0,
            ValueFromPipeline
        )]
        [Microsoft.GroupPolicy.Gpo]
        $GPO,

        [Parameter(
            ParameterSetName = 'GetAll',
            ValueFromPipelineByPropertyName
        )]
        [Parameter(
            ParameterSetName = 'ByName',
            ValueFromPipelineByPropertyName
        )]
        [Parameter(
            ParameterSetName = 'ByGUID',
            ValueFromPipelineByPropertyName
        )]
        [Alias( 'DomainName' )]
        [string]
        $Domain = $env:USERDNSDOMAIN,

        [Parameter(
            ParameterSetName = 'GetAll'
        )]
        [Parameter(
            ParameterSetName = 'ByName'
        )]
        [Parameter(
            ParameterSetName = 'ByGUID'
        )]
        [Alias( 'DC' )]
        [string]
        $Server,

        [Parameter(
            DontShow
        )]
        [switch]
        $AsDirectoryEntry

    )

    # if we are passed a GPO on the pipeline we do a special lookup
    if ( $GPO ) {

        if ( $GPO.WmiFilter ) {

            $GPO.WmiFilter.Path -replace '^MSFT_SomFilter\.' -split '(?<="),' | ForEach-Object {

                $Key, $Value = $_.Split('=').ForEach({ $_.Trim('"') })
                
                if ( $Key -eq 'ID' ) { $Guid = $Value }
                if ( $Key -eq 'Domain' ) { $Domain = $Value }

            }

            return Get-GPWmiFilter -Guid $Guid -Domain $Domain -AsDirectoryEntry:$AsDirectoryEntry.IsPresent


        } else {

            Write-Warning "No WMI filter attached to GPO"
            return
        }

    }

    if ( -not $Server ) {
    
        $DirectoryContext = [DirectoryContext]::new( 'Domain', $Domain )
        $Server = [Domain]::GetDomain( $DirectoryContext ).FindDomainController().Name
    
    }

    $NamingContext = ([adsi]"LDAP://$Domain/RootDSE").defaultNamingContext
    $SOMContainer = [adsi]"LDAP://$Server/CN=SOM,CN=WMIPolicy,CN=System,$NamingContext"
    $SearchFilter = switch ( $PSCmdlet.ParameterSetName ) {
        'GetAll' { '(objectclass=msWMI-Som)' }
        'ByName' { "(&(objectclass=msWMI-Som)(mswmi-name=$Name))" }
        'ByGUID' { "(&(objectclass=msWMI-Som)(mswmi-id=$($Guid.ToString('B'))))" }
    }
    
    $Searcher = [adsisearcher]::new( $SOMContainer, $SearchFilter )
    $Searcher.PropertiesToLoad.Add( 'adspath' ) > $null
    
    $DirectoryEntries = $Searcher.FindAll().ForEach({ [adsi]$_.Path })

    if ( -not $DirectoryEntries -and -not $All ) {

        Write-Error 'No WMI filter found!'
        return

    }
    
    if ( $AsDirectoryEntry ) {

        $DirectoryEntries

    } else {

        $DirectoryEntries | Select-Object `
            @{ N='DisplayName'  ; E={ $_.'mswmi-name' }},
            @{ N='DomainName'   ; E={ $Domain }},
            @{ N='Owner'        ; E={ $_.PsBase.ObjectSecurity.Owner }},
            @{ N='Id'           ; E={ [guid][string]$_.'mswmi-id' }},
            @{ N='Description'  ; E={ $_.'mswmi-parm1' }},
            @{ N='Author'       ; E={ $_.'mswmi-author' }},
            @{ N='Filters'      ; E={ , [WmiFilterList][string]$_.'mswmi-parm2' }}

    }

    $Searcher.Dispose()
    $SOMContainer.Dispose()
    
}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function New-GPWmiFilter {

    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'Low'
    )]
    param(

        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [Alias( 'DisplayName' )]
        [string]
        $Name,

        [Alias( 'Id' )]
        [guid]
        $Guid = ( [guid]::NewGuid() ),

        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [string]
        $Description,

        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [string]
        $Author = ([WindowsIdentity]::GetCurrent().Name),

        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [WmiFilterObject[]]
        $Filters,

        [Alias( 'DomainName' )]
        [string]
        $Domain = $env:USERDNSDOMAIN,

        [Alias( 'DC' )]
        [string]
        $Server
        
    )

    if ( -not $Server ) {
    
        $DirectoryContext = [DirectoryContext]::new( 'Domain', $Domain )
        $Server = [Domain]::GetDomain( $DirectoryContext ).FindDomainController().Name
    
    }

    $TestParams = @{
        Domain = $Domain
        Server = $Server
    }

    # Check for existing filter with same name
    if ( $ExistingFilter = Get-GPWmiFilter -Name $Name @TestParams -ErrorAction SilentlyContinue ) {
        
        Write-Error ( 'WMI filter ''{0}'' already exists in domain {1}!' -f $ExistingFilter.DisplayName, $Domain )
        return

    }
    
    # check for existing filter with same quid
    if ( $ExistingFilter = Get-GPWmiFilter -Guid $Guid @TestParams -ErrorAction SilentlyContinue ) {

        Write-Error ( 'WMI filter with GUID ''{0}'' already exists in domain {1}!' -f $ExistingFilter.ToString('B'), $Domain )
        return

    }

    $NamingContext = ([adsi]"LDAP://$Domain/RootDSE").defaultNamingContext
    $SOMContainer = [adsi]"LDAP://$Server/CN=SOM,CN=WMIPolicy,CN=System,$NamingContext"

    # create a time stamp
    $Created = [datetime]::UtcNow.ToString('yyyyMMddHHmmss.ffffff-000')

    if ( $PSCmdlet.ShouldProcess( $Name, 'create' ) ) {

        try {
            
            $WmiFilter = $SOMContainer.Create( 'msWMI-Som', "CN=$($Guid.ToString('B'))" )

            $WmiFilter.Put( 'msWMI-Name',               $Name                                       )
            $WmiFilter.Put( 'msWMI-Parm1',              $Description                                )
            $WmiFilter.Put( 'msWMI-Parm2',              [WmiFilterList]::new($Filters).ToString()   )
            $WmiFilter.Put( 'msWMI-Author',             $Author                                     )
            $WmiFilter.Put( 'msWMI-ID',                 $Guid.ToString('B')                         )
            $WmiFilter.Put( 'instanceType',             4                                           )
            $WmiFilter.Put( 'showInAdvancedViewOnly',   'TRUE'                                      )
            $WmiFilter.Put( 'distinguishedname',        "CN=$($Guid.ToString('B')),$SOMContainer"   )
            $WmiFilter.Put( 'msWMI-ChangeDate',         $Created                                    )
            $WmiFilter.Put( 'msWMI-CreationDate',       $Created                                    )

            $WmiFilter.SetInfo()

        } catch {

            Write-Error ( 'Failed to create WMI filter ''{0}'' in domain {1}. Please verify that you have rights to create WMI filters. You may also need to first enable ''Allow System Only Change'' for the domain. You can use the Set-ADSystemOnlyChange cmdlet to make this change.' -f $Name, $Domain )
        
        }

    }

    $SOMContainer.Dispose()

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Set-GPWmiFilter {

    [CmdletBinding(
        DefaultParameterSetName = 'ByName',
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    param(

        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory,
            Position = 0,
            ValueFromPipeline
        )]
        [Alias( 'DisplayName' )]
        [string]
        $Name,

        [Parameter(
            ParameterSetName = 'ByGUID',    
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [Alias( 'Id' )]
        [guid]
        $Guid,

        [string]
        $NewName,

        [string]
        $Description,

        [WmiFilterObject[]]
        $Filters,

        [Alias( 'DomainName' )]
        [string]
        $Domain = $env:USERDNSDOMAIN,

        [Alias( 'DC' )]
        [string]
        $Server
        
    )

    if ( -not $Server ) {
    
        $DirectoryContext = [DirectoryContext]::new( 'Domain', $Domain )
        $Server = [Domain]::GetDomain( $DirectoryContext ).FindDomainController().Name
    
    }

    $GetParams = @{
        Domain = $Domain
        Server = $Server
    }
    if ( $Name ) { $GetParams.Name = $Name }
    if ( $Guid ) { $GetParams.Guid = $Guid }

    # verify that the WMI filter exists
    $ExistingFilter = Get-GPWmiFilter @GetParams -AsDirectoryEntry -ErrorAction Stop

    $IsUpdated = $false

    if ( $NewName ) {

        $ExistingFilter.'msWMI-Name' = $NewName
        $IsUpdated = $true

    }

    if ( $Description ) {

        $ExistingFilter.'msWMI-Parm1' = $Description
        $IsUpdated = $true

    }

    if ( $Filters ) {

        $ExistingFilter.'msWMI-Parm2' = [WmiFilterList]::new( $Filters ).ToString()
        $IsUpdated = $true

    }

    if ( $IsUpdated ) {

        if ( $PSCmdlet.ShouldProcess( $ExistingFilter.'msWMI-Name', 'update' ) ) {

            try {

                $ExistingFilter.'msWMI-ChangeDate' = [datetime]::UtcNow.ToString('yyyyMMddHHmmss.ffffff-000')
                $ExistingFilter.CommitChanges()

            } catch {

                Write-Error ( 'Failed to update WMI filter ''{0}'' in domain {1}. Please verify that you have rights to create WMI filters. You may also need to first enable ''Allow System Only Change'' for the domain. You can use the Set-ADSystemOnlyChange cmdlet to make this change.' -f $ExistingFilter.'msWMI-Name', $Domain )

            }

        }
    
    } else {

        Write-Warning ( 'No changes made to WMI filter ''{0}'' in domain {1}.' -f $ExistingFilter.'msWMI-Name', $Domain )

    }

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Remove-GPWmiFilter {

    [CmdletBinding(
        DefaultParameterSetName = 'ByName',
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    param(

        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory,
            Position = 0,
            ValueFromPipeline
        )]
        [Alias( 'DisplayName' )]
        [string]
        $Name,

        [Parameter(
            ParameterSetName = 'ByGUID',    
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [Alias( 'Id' )]
        [guid]
        $Guid,

        [Alias( 'DomainName' )]
        [string]
        $Domain = $env:USERDNSDOMAIN,

        [Alias( 'DC' )]
        [string]
        $Server
        
    )

    if ( -not $Server ) {
    
        $DirectoryContext = [DirectoryContext]::new( 'Domain', $Domain )
        $Server = [Domain]::GetDomain( $DirectoryContext ).FindDomainController().Name
    
    }

    $GetParams = @{
        Domain = $Domain
        Server = $Server
    }
    if ( $Name ) { $GetParams.Name = $Name }
    if ( $Guid ) { $GetParams.Guid = $Guid }

    # verify that the WMI filter exists
    $ExistingFilter = Get-GPWmiFilter @GetParams -ErrorAction Stop

    $LookupBy = ( 'Name', 'GUID' )[ $PSBoundParameters.ContainsKey( 'Guid' ) ]
    $LookupValue = ( $Name, $Guid.ToString('B') )[ $PSBoundParameters.ContainsKey( 'Guid' ) ]

    # verify this WMI filter isn't in use
    $LinkedGPOs = Get-GPWmiFilterLinkedGPO @GetParams

    if ( $LinkedGPOs.Count -gt 0 ) {

        Write-Verbose 'Linked Group Policies:'



        $LinkedGPOs | ForEach-Object {

            Write-Verbose ( '{0} {1}' -f $_.DisplayName, $_.Id.ToString('B') )

        }

        Write-Error ( 'WMI filter with {0} ''{1}'' in domain {2} could not be removed! It is linked to {3} Group Policies.' -f $LookupBy, $LookupValue, $Domain, $LinkedGPOs.Count )
        return

    }

    if ( $PSCmdlet.ShouldProcess( $ExistingFilter.DisplayName, 'remove' ) ) {

        try {

            $NamingContext = ([adsi]"LDAP://$Domain/RootDSE").defaultNamingContext
            $SOMContainer = [adsi]"LDAP://$Server/CN=SOM,CN=WMIPolicy,CN=System,$NamingContext"
        
            $WmiFilterObject = $SOMContainer.Children.Find( "CN=$($ExistingFilter.Id.ToString('B'))" )

            $SOMContainer.Children.Remove( $WmiFilterObject )

        } catch {

            Write-Error ( 'Failed to remove WMI filter ''{0}'' in domain {1}. Please verify that you have rights to remove WMI filters. You may also need to first enable ''Allow System Only Change'' for the domain. You can use the Set-ADSystemOnlyChange cmdlet to make this change.' -f $ExistingFilter.DisplayName, $Domain )

        }

        $SOMContainer.Dispose()

    }

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Get-GPWmiFilterLinkedGPO {

    [CmdletBinding(
        DefaultParameterSetName = 'ByName'
    )]
    param(

        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory,
            Position = 0,
            ValueFromPipeline
        )]
        [Alias( 'DisplayName' )]
        [string]
        $Name,

        [Parameter(
            ParameterSetName = 'ByGUID',    
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [Alias( 'Id' )]
        [guid]
        $Guid,

        [Alias( 'DomainName' )]
        [string]
        $Domain = $env:USERDNSDOMAIN,

        [Alias( 'DC' )]
        [string]
        $Server
        
    )

    $DirectoryContext = [DirectoryContext]::new( 'Domain', $Domain )
    $DomainObject = [Domain]::GetDomain( $DirectoryContext )
    $Forest = $DomainObject.Forest.Name
        
    if ( $Server -and -not $DomainObject.FindAllDomainControllers().Where({ $_.Name -like "$Server*" }).IsGlobalCatalog() ) {
    
        Write-Error "The supplied server is not a Global Catalog"
        return
    
    } else {

        $Server = $DomainObject.Forest.FindGlobalCatalog().Name

    }

    $GetParams = @{
        Domain = $Domain
        Server = $Server
    }
    if ( $Name ) { $GetParams.Name = $Name }
    if ( $Guid ) { $GetParams.Guid = $Guid }

    # verify that the WMI filter exists
    $ExistingFilter = Get-GPWmiFilter @GetParams

    if ( -not $ExistingFilter ) {

        $LookupBy = ( 'Name', 'GUID' )[ $PSBoundParameters.ContainsKey( 'Guid' ) ]
        $LookupValue = ( $Name, $Guid.ToString('B') )[ $PSBoundParameters.ContainsKey( 'Guid' ) ]
        
        Write-Error ( 'WMI filter with {0} ''{1}'' could not be found in domain {2}!' -f $LookupBy, $LookupValue, $Domain )
        return

    }

    # get all linked GPOs
    $NamingContext = ([adsi]"LDAP://$Forest/RootDSE").defaultNamingContext

    $SearchFilter = "(&(objectclass=groupPolicyContainer)(gPCWQLFilter=*$($ExistingFilter.Id.ToString('B'))*))"
    $SearchRoot = [adsi]"LDAP://$Server/$NamingContext"
    
    $Searcher = [adsisearcher]::new( $SearchRoot, $SearchFilter )
    $Searcher.PropertiesToLoad.Add( 'name' ) > $null
    
    $Searcher.FindAll().ForEach({ $_.Properties.name }).ForEach({ Get-GPO -Guid $_ })

    $Searcher.Dispose()
    $SearchRoot.Dispose()

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Set-GPOWmiFilterLink {

    [CmdletBinding(
        DefaultParameterSetName = 'ByName',
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    param(

        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipeline
        )]
        [Microsoft.GroupPolicy.Gpo]
        $GPO,

        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory,
            Position = 1
        )]
        [Alias( 'DisplayName' )]
        [string]
        $Name,

        [Parameter(
            ParameterSetName = 'ByGUID',
            Mandatory,
            Position = 1
        )]
        [guid]
        $Guid,

        [Parameter(
            ParameterSetName = 'Clear',
            Mandatory,
            Position = 1
        )]
        [switch]
        $Clear,

        [Alias( 'DC' )]
        [string]
        $Server,

        [switch]
        $PassThru
        
    )

    # domain must be the same as the GPO
    $Domain = $GPO.DomainName

    if ( -not $Server ) {
    
        $DirectoryContext = [DirectoryContext]::new( 'Domain', $Domain )
        $Server = [Domain]::GetDomain( $DirectoryContext ).FindDomainController().Name
    
    }

    # get the GPO as a directory entry
    $GPODirectoryEntry = Get-GPODirectoryEntry $GPO

    if ( $Clear ) {

        $GPODirectoryEntry.PutEx( 1, 'gPCWQLFilter', 0 )

    } else {

        # get the GPWmiFilter object
        $GetParam = @{}
        if ( $Name ) { $GetParam.Name = $Name }
        if ( $Guid ) { $GetParam.Guid = $Guid }
        $WmiFilterObject = Get-GPWmiFilter @GetParam -Domain $Domain -Server $Server -ErrorAction Stop

        # assign the value
        $GPODirectoryEntry.gPCWQLFilter = "[$Domain;$($WmiFilterObject.Id.ToString('B'));0]"

    }

    if ( $PSCmdlet.ShouldProcess( $GPO.DisplayName, 'set WMI filter' ) ) {

        try {

            $GPODirectoryEntry.CommitChanges()

        } catch {

            Write-Error ( 'Failed to update GPO ''{0}'' in domain {1}. Please verify that you have rights to edit this GPO.' -f $GPO.DisplayName, $GPO.DomainName )
            return

        }

    }

    if ( $PassThru ) {

        $GPO | Get-GPO

    }

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Get-GPODirectoryEntry {

    param(

        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipeline
        )]
        [Microsoft.GroupPolicy.Gpo]
        $GPO
    )

    $Domain = $GPO.DomainName
    $DirectoryContext = [DirectoryContext]::new( 'Domain', $Domain )
    $Server = [Domain]::GetDomain( $DirectoryContext ).FindDomainController().Name
    
    $NamingContext = ([adsi]"LDAP://$Domain/RootDSE").defaultNamingContext
    [adsi]"LDAP://$Server/CN=$($GPO.Id.ToString('B')),CN=Policies,CN=System,$NamingContext"

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Set-ADSystemOnlyChange {

    param(

        [Parameter(Mandatory, ValueFromPipeline, Position=1, ParameterSetName='Enable_Remote')]
        [Parameter(Mandatory, ValueFromPipeline, Position=1, ParameterSetName='Disable_Remote')]
        [string[]]
        $ComputerName,

        [Parameter(Mandatory, ParameterSetName='Enable_Local')]
        [Parameter(Mandatory, ParameterSetName='Enable_Remote')]
        [switch]
        $Enable,

        [Parameter(Mandatory, ParameterSetName='Disable_Local')]
        [Parameter(Mandatory, ParameterSetName='Disable_Remote')]
        [switch]
        $Disable,

        [Parameter(ParameterSetName='Enable_Remote')]
        [Parameter(ParameterSetName='Disable_Remote')]
        [pscredential]
        $Credential

    )

    begin {

        $CommandSplat = @{}

        if ( $Credential ) { $CommandSplat.Credential = $Credential }

        [ArrayList]$Computers = @()

    }

    process {

        $ComputerName.ForEach({ $Computers.Add( $_ ) > $null })

    }

    end {

        if ( $Computers.Count -gt 0 ) {

            [string[]]$CommandSplat.ComputerName = $Computers

        }

        if ( $Enable ) {

            Write-Warning "Enabling the setting 'Allow System Only Change' should be a temporary measure. Don't foreget to revert this setting once changes are applied."

        }

        Invoke-Command @CommandSplat -ScriptBlock {

            # check for NTDS service
            if ( -not( Get-Service NTDS -ErrorAction SilentlyContinue ) ) {

                throw 'Must be run on a domain controller!'

            }

            # check for and create if missing the Parameters registry key
            $ParametersKey = Get-Item -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -ErrorAction SilentlyContinue

            if ( -not $ParametersKey ) {
        
                New-Item -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -ItemType RegistryKey > $null
        
            }

            # check if 'Allow System Only Change' value exists
            $SystemOnlyChangeValue = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -Name 'Allow System Only Change' -ErrorAction SilentlyContinue

            if ( -not $SystemOnlyChangeValue ) {

                New-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -Name 'Allow System Only Change' -Value ([int]$Using:Enable.IsPresent) -PropertyType DWORD > $null

            } else {

                Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -Name 'Allow System Only Change' -Value ([int]$Using:Enable.IsPresent) > $null
            
            }

        }

    }

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Test-ADSystemOnlyChangeEnabled {

    [CmdletBinding(DefaultParameterSetName='Local')]
    param(

        [Parameter(Mandatory, ValueFromPipeline, Position=1, ParameterSetName='Remote')]
        [string[]]
        $ComputerName,

        [Parameter(ParameterSetName='Remote')]
        [pscredential]
        $Credential

    )

    begin {

        $CommandSplat = @{}

        if ( $Credential ) { $CommandSplat.Credential = $Credential }

        [ArrayList]$Computers = @()

    }

    process {

        $ComputerName.ForEach({ $Computers.Add( $_ ) > $null })

    }

    end {

        if ( $Computers.Count -gt 0 ) {

            [string[]]$CommandSplat.ComputerName = $Computers

        }

        Invoke-Command @CommandSplat -ScriptBlock {

            # check for NTDS service
            if ( -not( Get-Service NTDS -ErrorAction SilentlyContinue ) ) {

                throw 'Must be run on a domain controller!'

            }

            # check for and create if missing the Parameters registry key
            $ParametersKey = Get-Item -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -ErrorAction SilentlyContinue

            if ( -not $ParametersKey ) {
        
                return $false
        
            }

            # check if 'Allow System Only Change' value exists
            $SystemOnlyChangeValue = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -Name 'Allow System Only Change' -ErrorAction SilentlyContinue |
                Select-Object -ExpandProperty 'Allow System Only Change'

            return [bool]$SystemOnlyChangeValue

        }

    }

}