DnsStig.psm1

Function Set-DnsServerStig {
    <#
        .SYNOPSIS
            Implements all STIGs contained in this module within the specified forest.
 
        .DESCRIPTION
            The Set-DnsServerStig cmdlet implements all of the STIGs in this module.
 
        .PARAMETER Forest
            The DNS Forest to configure.
 
        .PARAMETER RemoveProhibitedRecords
            Option to remove found prohibited records of type HINFO, RP, TXT, and LOC.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Set-DnsServerStig
 
            Implements all STIGs contained in this module within the specified forest, but does not removed prohibited record types.
 
        .EXAMPLE
            PS C:\>Set-DnsServerStig -RemoveProhibitedRecords
 
            Implements all STIG settings contained in this module and removes prohibited record types.
     
        .INPUTS
            System.String, System.Management.Automation.SwitchParameter, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>

    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [switch]$RemoveProhibitedRecords = $false,
        [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty  
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Starting DNS STIG"
    }

    Process
    {
        if ($RemoveProhibitedRecords)
        {
            Remove-DnsServerProhibitedRecords -Forest $Forest -Credential $Credential
        }
        else
        {
            Get-DnsServerProhibitedRecords -Forest $Forest -Credential $Credential | ForEach-Object {
                Write-Warning ($_.Record | Out-String)
            }
        }

        Get-DnsServersRunningIPv6 -Forest $Forest -Credential $Credential | ForEach-Object {
            Write-Warning  $_.ComputerName
        }

        Get-InactiveDnsServers -Forest $Forest | ForEach-Object {
            Write-Warning "Inactive server: $_"
        }

        Set-DnsServerZoneSecureUpdates -Forest $Forest -Credential $Credential
        Set-DnsServerDisableRootHints -Forest $Forest -Credential $Credential
        Set-DnsServerRecursionAndForwarders -Forest $Forest -Credential $Credential
        Set-DnsServerCryptoFolderPermissions -Forest $Forest -Credential $Credential
        Set-DnsServerZoneTransfers -Forest $Forest -Credential $Credential
        Set-DnsServerLogPermissions -Forest $Forest -Credential $Credential
        Set-DnsServerLogging -Forest $Forest -Credential $Credential
        Set-DnsServerVersionQuery -Forest $Forest -Credential $Credential

        Remove-DnsServerZoneIPv6LinkLocalAddresses -Forest $Forest -Credential $Credential
    }

    End {}
}

Function Remove-DnsServerProhibitedRecords {
    <#
        .SYNOPSIS
            The HINFO, RP, TXT and LOC RR types must not be used in the zone SOA.
 
        .DESCRIPTION
            The Remove-DnsServerProhibitedRecords cmdlet gets and then removes all prohibited records from each zone in the forest.
 
        .PARAMETER Forest
            The DNS Forest to configure.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Remove-DnsServerProhibitedRecords
 
            Removes all of the prohibited records in the current user forest.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-SI-000004
            Rule ID
                SV-73169r1
            Vuln ID
                V-58739
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {    
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Removing DNS Server Prohibited Entries."
    }

    Process
    {
        Get-DnsServerProhibitedRecords -Forest $Forest -Credential $Credential | ForEach-Object {
            $Record = ($_.Record | Out-String)
            Write-Warning "Removing record $Record" 

            try
            {
                $Server = Resolve-DnsName -Name $_.ZoneName -ErrorAction Stop | Select-Object -ExpandProperty PrimaryServer
                
                if ([System.String]::IsNullOrEmpty($Server)) {
                    Write-Warning "PrimaryServer attribute was null, using the zone name for the server."
                    $Server = $_.ZoneName
                }

                Remove-DnsServerResourceRecord -ZoneName $_.ZoneName -ComputerName $Server -InputObject $_.Record -Force -Confirm:$false
            }
            catch [Exception]
            {
                Write-Warning "Error removing record $Record`: $($_.ToString())"
            }
        }
    }

    End 
    {
        Write-Host "Removing prohibited entries complete."
    }
}

Function Get-DnsServerProhibitedRecords {
    <#
        .SYNOPSIS
            The HINFO, RP, TXT and LOC RR types must not be used in the zone SOA.
 
        .DESCRIPTION
            The Get-DnsServerProhibitedRecords cmdlet gets all prohibited records from each zone in the forest.
 
        .PARAMETER Forest
            The DNS Forest to search.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Get-DnsServerProhibitedRecords
 
            Gets all of the prohibited records in the current user forest.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            PSObject[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-SI-000004
            Rule ID
                SV-73169r1
            Vuln ID
                V-58739
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        Write-Host "Getting DNS Server prohibited entries."
    }

    Process
    {
        $BadRecords = @()

        Get-ForestDnsZones -ZoneType All -LookupType Forward -Forest $Forest -Credential $Credential | ForEach-Object {
            
            try
            {
                Write-Host "Getting entries for zone $($_.ZoneName)"
                $Zone = $_.ZoneName
                $Server = Resolve-DnsName -Name $_.ZoneName -ErrorAction Stop | Select-Object -ExpandProperty PrimaryServer
                
                if ([System.String]::IsNullOrEmpty($Server)) {
                    Write-Warning "PrimaryServer attribute was null, using the zone name for the server."
                    $Server = $_.ZoneName
                }

                $Records = @(Get-DnsServerResourceRecord -ComputerName $Server -ZoneName $_.ZoneName | Where-Object {$_.RecordType -in @("Hinfo","RP","TXT","LOC")})

                if ($Records.Count -gt 0)
                {
                    foreach ($Record in $Records)
                    {
                        $BadRecords += @{Record=($Record);ZoneName=$_.ZoneName}
                    }
                }
            }
            catch [Exception]
            {
                Write-Warning "Error getting resource records for $Zone : $($_.ToString())"
            }
        }
    }

    End 
    {
        Write-Output $BadRecords
    }
}

Function Get-DnsServersRunningIPv6 {
    <#
        .SYNOPSIS
            When IPv6 protocol is installed, the server must also be configured to answer for IPv6 AAAA records.
 
        .DESCRIPTION
            The Get-DnsServersRunningIPv6 checks each DNS server in the forest to see if it has IPv6 enabled. If it does, but does not host AAAA records, it is returned as part of an array.
 
        .PARAMETER Forest
            The DNS Forest to test.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Get-DnsServersRunningIPv6
 
            Gets all of the DNS servers in the forest running IPv6 and not hosting AAAA records.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            PSObject[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
     
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-CM-000028
            Rule ID
                SV-73057r1
            Vuln ID
                V-58627
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        Write-Host "Getting DNS Servers running IPv6 and not hosting AAAA records."
    }

    Process 
    {
        #0xFFFFFFFF - IPv6 Disabled On All Interfaces
        #0xFFFFFFFE - IPv6 Enabled only on tunnel interfaces
        #0xFFFFFFEF - IPv6 Disabled On Tunnel Interfaces, Enabled On All Others
        #0xFFFFFFEE - IPv6 Disabled On Loopback Interface, Enabled On All Others
        #0xFFFFFFDF - IPv6 Disabled, Prefer IPv6 over IpV4
        #0xFFFFFFDE - IPv6 Enabled Only On Tunnel Interfaces, Prefer IPv6 of IPv4
        #0xFFFFFFCF - IPv6 Enabled On All Non Tunnel Interfaces, Prefer IPv6 over IPv4
        #0xFFFFFFCE - IPv6 Disabled On Loopback Interface, Prefer IPv6 over IPv4

        #0x000000FF - IPv6 Disabled On All Interfaces
        #0x00000020 - IPv6 Prefer IPv4 over IPv6 by changing entries in prefix policy table = 100000
        #0x00000010 - IPv6 Disabled on LAN and PPP interfaces = 010000
        #0x00000008 - Disable Teredo = 001000
        #0x00000004 - Disable ISATAP = 000100
        #0x00000002 - Disable 6to4 = 000010
        #0x00000001 - IPv6 Disabled on Tunnel Interfaces including ISATAP, 6to4 and Teredo = 000001

        $DnsServers = Get-ForestDnsServers -Forest $Forest -Credential $Credential
        $Zones = Get-ForestDnsZones -Forest $Forest -LookupType Forward -Credential $Credential
        $BadZones = @()

        foreach ($Server in $DnsServers) 
        {
            $RegPropertyExists = Invoke-Command -ComputerName $Server -ScriptBlock ${function:Test-RegistryEntry} -ArgumentList "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters","DisabledComponents" -Credential $Credential

            if ($RegPropertyExists)
            {
                $State = Invoke-Command -ComputerName $Server -ScriptBlock {Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters" -Name DisabledComponents} -Credential $Credential
                $DisabledValues = @([System.Convert]::ToString(0xFFFFFFFF, 16), [System.Convert]::ToString(0xFFFFFFFE, 16), [System.Convert]::ToString(0x00000011, 16))
            }

            #If IPv6 isn't disabled on the entire system check each individual adapter
            if (-not $RegPropertyExists -or (-not $DisabledValues.Contains([System.Convert]::ToString($State.DisabledComponents, 16))))
            {
                #Will return the number of network adapters with IPv6 Binding enabled
                $Adapters = @(Invoke-Command -ComputerName $Server -ScriptBlock {Get-NetAdapter | Where-Object{ (Get-NetAdapterBinding -InterfaceDESCRIPTION $_.InterfaceDESCRIPTION -ErrorAction SilentlyContinue | Where-Object {$_.ComponentID -eq "ms_tcpip6" -and $_.Enabled -eq $true})}} -Credential $Credential)
                
                if ($Adapters.Count -gt 0)
                {
                    Get-SpecificServerDnsZones -ComputerName $Server -LookupType Forward | ForEach-Object {
                        if (@(Get-DnsServerResourceRecord -ComputerName $Server -RRType AAAA -ZoneName $_.ZoneName).Count -eq 0)
                        {
                            $BadZones += @{ComputerName = $Server;ZoneName=$_.ZoneName;Adapters=$Adapters}
                        }
                    }
                }
            }
        }
    }

    End 
    {
        if ($BadZones.Count -gt 0)
        {
            Write-Warning "Found servers without AAAA records that have IPv6 enabled."
        }
        
        Write-Output $BadZones
    }
}

Function Set-DnsServerNotifySecondaryServers {
    <#
        .SYNOPSIS
            The Windows 2012 DNS Server must use DNS Notify to prevent denial of service through increase in workload.
 
        .DESCRIPTION
            The Set-DnsServerNotifySecondaryServers cmdlet ensures the Notify secondary servers is enabled for any DNS zone that allows zone transfers.
 
        .PARAMETER Forest
            The DNS Forest to configure.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Set-DnsServerNotifySecondaryServers
 
            Ensures Notify is enabled for any DNS servers allowing zone transfers to secondaries.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 12/8/2015
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-SC-000027
            Rule ID
                SV-73129r1
            Vuln ID
                V-58699
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Setting secondary server notification settings."
    }

    Process
    {
        $Servers = Get-ForestDnsServers -Forest $Forest -Credential $Credential

        foreach ($Server in $Servers)
        {
            Get-SpecificServerDnsZones -ComputerName $Server -ZoneType Primary -Credential $Credential | ForEach-Object {
                if ($_.SecureSecondaries -ne "NoTransfer")
                {
                    Set-DnsServerPrimaryZone -Name $_.ZoneName -ComputerName $Server -Notify Notify
                }
            }
        }
    }

    End 
    {
        Write-Host "Completed setting secondary server notification settings."
    }
}

Function Remove-DnsServerWINSForwardLookup {
    <#
        .SYNOPSIS
            WINS lookups must be disabled on the Windows 2012 DNS Server.
 
        .DESCRIPTION
            The Remove-DnsServerWINSForwardLookup disabled WINS forward lookup on each DNS server in the forest.
 
        .PARAMETER Forest
            The DNS Forest to configure.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Remove-DnsServerWINSForwardLookup
 
            Disables WINS lookups on all DNS servers in the forest.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-SC-000006
            Rule ID
                SV-73091r1
            Vuln ID
                V-58661
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Setting WINS server settings."
    }

    Process
    {
        $Servers = Get-ForestDnsServers -Forest $Forest

        foreach ($Server in $Servers)
        {
            Get-SpecificServerDnsZones -ComputerName $Server -ZoneType Primary -LookupType Forward | ForEach-Object {
                #TODO: Set WINS Setting
            }
        }
    }

    End 
    {
        Write-Host "Completed WINS server settings."
    }
}

Function Set-DnsServerCryptoFolderPermissions {
    <#
        .SYNOPSIS
            The Windows 2012 DNS Server must be configured to enforce authorized access to the corresponding private key.
 
        .DESCRIPTION
            The Set-DnsServerCryptoFolderPermissions sets %ALLUSERSPROFILE%\Microsoft\Crypto folder, subfolders, and files to Full Control for SYSTEM and Administrators and removes all other privileges.
            The owner for the folder, subfolders, and files is also set to BUILTIN\Administrators.
 
        .PARAMETER Forest
            The DNS Forest to configure.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
         
        .EXAMPLE
            PS C:\>Set-DnsServerCryptoFolderPermissions
 
            Sets The permissions and owner for the crypto directory.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-IA-000006
                WDNS-IA-000007
                WDNS-IA-000008
            Rule ID
                SV-73071r1
                SV-73073r1
                SV-73075r1
            Vuln ID
                V-58641
                V-58643
                V-58645
            Severity
                CAT II
#>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )
    
    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Setting Dns Server crypto folder owner and permissions."
    }

    Process
    {
        $Servers = Get-ForestDnsServers -Forest $Forest -Credential $Credential
        $Path = "$env:ALLUSERSPROFILE\Microsoft\Crypto"
        [System.Security.AccessControl.FileSystemAccessRule[]]$Rules = New-CryptoFolderAccessRuleSet

        foreach ($Server in $Servers)
        {
            Write-Host "Setting file permissions on $Server"

            $ServerPath = "\\$Server\" + $Path.Replace(":\","$\")
            Set-FilePermissions -Path $ServerPath -Rules $Rules -Replace -ForceInheritance
            Write-Host "Setting folder owner on $Server"
            Invoke-Command -ComputerName $Server -Scriptblock ${function:Set-Owner} -ArgumentList $Path,"BUILTIN\Administrators",$true
        }
    }

    End 
    {
        Write-Host "Completed setting crypto folder owner and permissions."
    }
}

Function Set-DnsServerLogPermissions {
    <#
        .SYNOPSIS
            The Windows 2012 DNS Server logging criteria must only be configured by the ISSM or individuals appointed by the ISSM.
 
        .DESCRIPTION
            The cmdlet assigns specific access permissions for the DNS server logs The command must be run with Enterprise Admin credentials.
 
        .PARAMETER Forest
            The Forest in which to configure DNS servers.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Set-DnsServerLogPermissions
 
            Sets the c:\windows\system32\winevt folder to the default permissions and forces inheritance on all log files.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-AU-000007
            Rule ID
                SV-72983r1
            Vuln ID
                V-58553
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )
    
    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Setting Dns Server log permissions."
    }

    Process
    {
        $Servers = Get-ForestDnsServers -Forest $Forest -Credential $Credential
        $Path = "$env:SYSTEMROOT\System32\Winevt\Logs"
        $Rules = New-EventLogAccessRuleSet

        foreach ($Server in $Servers)
        {
            Write-Host $Server
            $ServerPath = "\\$Server\" + $Path.Replace(":\","$\")
            Set-FilePermissions -Path $ServerPath -Rules $Rules -Replace -ForceInheritance
        }
    }

    End 
    {
        Write-Host "Completed setting Dns Server log permissions."
    }
}

Function Set-DnsServerZoneTransfers {
    <#
        .SYNOPSIS
            The Windows DNS primary server must only send zone transfers to a specific list of secondary name servers.
 
        .DESCRIPTION
            The Set-DnsServerZoneTransfers cmdlet checks each DNS server in the forest to see if it has zone transfers enabled. If it does and are allowed "To any server", then the setting is changed to "Only to servers on the Name Servers tab".
 
        .PARAMETER Forest
            The DNS Forest to configure.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Set-DnsServerZoneTransfers
 
            Sets all DNS servers in the forest that have zone transfers enabled to any server to only servers on the Name Servers tab.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-IA-000004
            Rule ID
                SV-73067r1
            Vuln ID
                V-58637
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }
        
        Write-Host "Setting DNS Server Zone Transfer settings."
    }

    Process
    {
        $Servers = Get-ForestDnsServers -Forest $Forest -Credential $Credential

        foreach ($Server in $Servers)
        {
            Get-SpecificServerDnsZones -ComputerName $Server -ZoneType Primary | ForEach-Object {
                if ($_.SecureSecondaries -eq "TransferAnyServer")
                {
                    Write-Warning "DNS Server $Server was set to transfer to any server, fixing..."
                    Set-DnsServerPrimaryZone -Name $_.ZoneName -ComputerName $Server -SecureSecondaries TransferToZoneNameServer
                    Write-Host "Done fixing $Server."
                }
            }
        }
    }

    End 
    {
        Write-Host "Completed setting DNS Server zone transfer settings"
    }
}

Function Remove-DnsServerZoneIPv6LinkLocalAddresses {
    <#
        .SYNOPSIS
            Non-routable IPv6 link-local scope addresses must not be configured in any zone.
 
        .DESCRIPTION
            The Remove-DnsServerZoneIPv6LinkLocalAddresses cmdlet gets all AAAA records in each zone and removes any link-local scope addresses.
 
        .PARAMETER Forest
            The DNS Forest to configure.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Remove-DnsServerZoneIPv6LinkLocalAddresses
             
            Removes all IPv6 link-local addresses from all DNS Zones
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-CM-000026
            Rule ID
                SV-73053r1
            Vuln ID
                V-58623
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Removing IPv6 link local addresses from DNS."
    }

    Process
    {
        $Zones = @()

        Get-ForestDNSServers -Forest $Forest -Credential $Credential | ForEach-Object {
            $Zones += Get-DnsServerZone -ComputerName $_ -WarningAction SilentlyContinue | Where-Object {$_.IsReverseLookupZone -eq $false}
        }

        $Zones | Group-Object -Property ZoneName | ForEach-Object {
            $Zone = $_.Name
            $Zone
            try
            {
                $Server = Resolve-DnsName -Name $Zone -ErrorAction Stop | Select-Object -ExpandProperty PrimaryServer
                
                if ([System.String]::IsNullOrEmpty($Server)) {
                    Write-Warning "PrimaryServer attribute was null, using the zone name for the server."
                    $Server = $Zone
                }

                $RemovedRecords = Get-DnsServerResourceRecord -ZoneName $Zone -RRType AAAA -ComputerName $Server | Where-Object {$_.RecordData -match "^FE[89AB].*$"} | Remove-DnsServerResourceRecord -ComputerName $Zone -ZoneName $Zone -Force -Confirm:$false -PassThru
                if ($RemovedRecords.Count -gt 0)
                {
                    Write-Warning "Removed the following records:"
                    Write-Warning $RemovedRecords
                }
                else
                {
                    Write-Host "No records to remove."
                }
            }
            catch [Exception]
            {
                Write-Warning "Could not get records for zone: $Zone`: $($_.ToString())"
            }
        }
    }

    End
    {
        Write-Host "Completed removing IPv6 link local addresses."
    }
}

Function Get-InactiveDnsServers {
    <#
        .SYNOPSIS
            The Windows 2012 DNS Servers zone files must have NS records that point to active name servers authoritative for the domain specified in that record.
 
        .DESCRIPTION
            The Get-InactiveDnsServers cmdlet gets all NS records for a forest and then tries to resolve the NS record hostname on that NS server. Any non-active DNS servers are returned.
 
        .PARAMETER Forest
            The DNS Forest to test against.
     
        .EXAMPLE
            PS C:\>Get-InactiveDnsServers
 
            Returns a list of any non-active DNS servers in the forest.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            System.String[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-CM-000010
            Rule ID
                SV-73023r1
            Vuln ID
                V-58593
            Severity
                CAT I
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty
    )

    Begin 
    {
        $InactiveServers = @()
        Write-Host "Getting all inactive DNS servers."
    }

    Process
    {
        Get-ForestDnsServers -Forest $Forest | ForEach-Object {
            $Server = $_
            try
            {
                Resolve-DnsName -Name $_ -Server $_ -ErrorAction Stop | Out-Null
            }
            catch [Exception]
            {
                $InactiveServers += $Server
            }
        }
    }

    End
    {
        Write-Output $InactiveServers
    }
}

Function Set-DnsServerDisableRootHints {
    <#
        .SYNOPSIS
            Forwarders on an authoritative Windows 2012 DNS Server, if enabled for external resolution, must only forward to either an internal, non-AD-integrated DNS server or to the DoD Enterprise Recursive Services (ERS).
 
        .DESCRIPTION
            The Set-DnsServerDisableRootHints cmdlet removes all root hints and disables using root hints on each DNS server in the forest. The command must be run with Enterprise Admin credentials.
 
        .PARAMETER Forest
            The DNS Forest to set the root hints settings on.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Set-DnsServerDisableRootHints
 
            Disables root hints on all servers and removes root hints on all servers.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-CM-000004
                WDNS-CM-000022
            Rule ID
                SV-73011r1
                SV-73045r1
            Vuln ID
                V-58581
                V-58615
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Disabling all DNS server root hints."
    }

    Process
    {
        $Servers = Get-ForestDnsServers -Forest $Forest -Credential $Credential

        foreach ($Server in $Servers)
        {
            Get-DnsServerRootHint -ComputerName $Server | Remove-DnsServerRootHint -ComputerName $Server -Force -Confirm:$false
            Invoke-Command -ComputerName $Server -ScriptBlock {Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters" -Name "IsSlave" -Value 1 } -Credential $Credential
        }
    }

    End{}
}

Function Set-DnsServerRecursionAndForwarders {
    <#
        .SYNOPSIS
            The Windows 2012 DNS Server must prohibit recursion on authoritative name servers for which forwarders have not been configured for external queries.
 
        .DESCRIPTION
            The Set-DnsServerRecursionAndForwarders cmdlet checks to see if forwarders are disabled, if they are it also disables recursion. The command must be run with Enterprise Admin credentials.
 
        .PARAMETER Forest
            The DNS Forest to set the recursion setting on.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Set-DnsServerRecursionAndForwarders
 
            Disables recursion on all DNS servers without forwarders
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-CM-000003
            Rule ID
                SV-73009r1
            Vuln ID
                V-58579
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Setting DNS recursion and forwarders settings."
    }

    Process 
    {
        $Servers = Get-ForestDnsServers -Forest $Forest -Credential $Credential

        foreach ($Server in $Servers)
        {
            $Forwarders = Get-DnsServerForwarder -ComputerName $Server

            if ($Forwarders.IPAddress.Count -eq 0)
            {
                Set-DnsServerRecursion -ComputerName $Server -Enable $false
            }
        }
    }

    End 
    {
        Write-Host "Completed setting recursions and forwarders."
    }
}

Function Set-DnsServerZoneSecureUpdates {
    <#
        .SYNOPSIS
            The Windows 2012 DNS Server must restrict incoming dynamic update requests to known clients.
 
        .DESCRIPTION
            The Set-DnsServerZoneSecureUpdates cmdlet turns on secure updates only for all DNS servers in the forest. The command must be run with Enterprise Admin credentials.
 
        .PARAMETER Forest
            The DNS Forest to set secure updates on.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Set-DnsServerZoneSecureUpdates
 
            Enforces secure updates on each primary zone in the current user forest.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-AC-000001
                WDNS-IA-000001
            Rule ID
                SV-72667r1
                SV-73061r1
            Vuln ID
                V-58237
                V-58631
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Setting secure updates on Dns Servers."
    }

    Process 
    {
        Get-ForestDnsZones -Forest $Forest -ZoneType Primary -DsIntegration DsIntegrated -Credential $Credential | ForEach-Object {
            Write-Host "Configuring secure dynamic updates on $($_.ZoneName)"
            
            $Zone = $_.ZoneName
            try
            {
                $Server = Resolve-DnsName -Name $_.ZoneName -ErrorAction Stop | Select-Object -ExpandProperty PrimaryServer
                
                if ([System.String]::IsNullOrEmpty($Server)) {
                    Write-Warning "PrimaryServer attribute was null, using the zone name for the server."
                    $Server = $_.ZoneName
                }

                Set-DnsServerPrimaryZone -Name $_.ZoneName -DynamicUpdate Secure -ComputerName $Server
            }
            catch [Exception]
            {
                Write-Warning "Error setting secure dynamic updates on $Zone`: $($_.ToString())"
            }
        }
    }

    End 
    {
        Write-Host "Completed setting secure updates."
    }
}

Function Set-DnsServerLogging {
    <#
        .SYNOPSIS
            The Windows 2012 DNS Server must be configured to record, and make available to authorized personnel, who added/modified/deleted DNS zone information.
 
        .DESCRIPTION
            The cmdlet turns on logging and log rollover on every DNS server in the forest. The command must be run with Enterprise Admin credentials.
 
        .PARAMETER Forest
            The DNS Forest to set secure updates on.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Set-DnsServerLogging
 
            Enables logging on every DNS server in the current user's forest.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-AU-000001
                WDNS-AU-000005
                WDNS-AU-000006
                WDNS-AU-000007
                WDNS-AU-000008
                WDNS-AU-000010
                WDNS-AU-000011
                WDNS-AU-000012
                WDNS-AU-000013
                WDNS-AU-000014
                WDNS-AU-000015
                WDNS-SI-000009
            Rule ID
                SV-72973r1
                SV-72979r1
                SV-72981r1
                SV-72983r1
                SV-72985r1
                SV-72991r1
                SV-72993r1
                SV-72995r1
                SV-72997r1
                SV-72999r1
                SV-73001r1
                SV-73149r1
            Vuln ID
                V-58543
                V-58549
                V-58551
                V-58553
                V-58555
                V-58561
                V-58563
                V-58565
                V-58567
                V-58569
                V-58571
                V-58719
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Setting Dns Server logging."
    }

    Process
    {
        Get-ForestDnsServers -Forest $Forest -Credential $Credential | ForEach-Object {
            Write-Host $_
            Set-DnsServerDiagnostics -ComputerName $_ -All $true
            Set-DnsServerDiagnostics -EnableLogFileRollover $true
        }
    }

    End 
    {
        Write-Host "Completed setting Dns Server logging."
    }
}

Function Set-DnsServerVersionQuery {
    <#
        .SYNOPSIS
            The DNS Name Server software must be configured to refuse queries for its version information.
 
        .DESCRIPTION
            The Set-DnsServerVersionQuery cmdlet disables returning version information from a DNS query on all DNS servers in the Forest. The command must be run with Enterprise Admin credentials.
 
        .PARAMETER Forest
            The forest to configure.
 
        .PARAMETER Credential
            The credential to use, requires Enterprise Admin rights.
 
        .EXAMPLE
            PS C:\>Set-DnsServerVersionQuery
 
            Disables the return of version information from DNS queries.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
 
        .FUNCTIONALITY
            STIG
                Microsoft Windows 2012 Server DNS V1R2
            STIG ID
                WDNS-SI-000003
            Rule ID
                SV-73167r1
            Vuln ID
                V-58737
            Severity
                CAT II
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty  
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        if (!(Test-IsEnterpriseAdmin -Credential $Credential))
        {
            Write-Error "The cmdlet must be run with Enterprise Admin credentials."
            Exit 1
        }

        Write-Host "Setting Dns Server version query settings."
    }

    Process
    {
        Get-ForestDnsServers -Forest $Forest -Credential $Credential |  ForEach-Object {
            $Server = $_
            Write-Host $Server
            try
            {
                Invoke-Command -ComputerName $_ -ScriptBlock {Set-ItemProperty -Path "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\DNS\\Parameters" -Name "EnableVersionQuery" -Value 0} -Credential $Credential
            }
            catch [Exception] 
            {
                Write-Host "Error on $Server`: $($_.ToString())"
            }
        }
    }
    End 
    {
        Write-Host "Completed setting Dns Server version query settings."
    }
}

Function Get-ForestDnsServers {
    <#
        .SYNOPSIS
            Finds all of the DNS servers for the current forest.
 
        .DESCRIPTION
            Finds all of the DNS servers registered as NS servers in the forest and returns an array of DNS names.
 
        .PARAMETER Forest
            Specify the forest to get the name servers from.
 
        .PARAMETER Credential
            The credential to use to query for the current AD Forest object.
 
        .EXAMPLE
            PS C:\>Get-ForestDnsServers
 
            Gets all of the DNS server in the current forest.
 
        .INPUTS
            System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            System.String[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>


    Param (
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin 
    {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        try {
            Import-Module ActiveDirectory
        }
        catch [Exception] {
            Write-Warning "This cmdlet requires the Active Directory module."
            Exit 1
        }
    }

    Process
    {
        if ($Forest -eq [System.String]::Empty)
        {
            if ($Credential -ne [PSCredential]::Empty)
            {
                $RootDomain = (Get-ADForest -Current $Credential.UserName -Credential $Credential).RootDomain
            }
            else
            {
                $RootDomain = (Get-ADForest -Current LoggedOnUser).RootDomain
            }
        }
        else
        {
            $RootDomain = $Forest
        }

        Write-Output (Resolve-DnsName -Name $RootDomain -Type NS | Where-Object {![System.String]::IsNullOrEmpty($_.NameHost)} | Select-Object -ExpandProperty NameHost)
    }

    End {}
}

Function Get-ForestDnsZones {
    <#
        .SYNOPSIS
            Get all of the forward lookup zones in a forest.
 
        .DESCRIPTION
            The Get-ForestDnsZones cmdlet gets all of the primary forward lookup zones for a forest.
 
        .PARAMETER Forest
            The DNS Forest to search.
 
        .PARAMETER ZoneType
            Select from All, Primary, Seconday, or Stub. Defaults to All.
 
        .PARAMETER DsIntegration
            Select from All, DsIntegrated, or NonDsIntegrated. Defaults to All.
 
        .PARAMETER LookupType
            Select from All, Forward, or Reverse. Defaults to All.
 
        .PARAMETER Credential
            The credential to use.
 
        .EXAMPLE
            PS C:\>Get-ForestDnsZones
 
            Gets all of the dns zones in the current user's forest.
 
        .EXAMPLE
            PS C:\>Get-ForestDnzZones -ZoneType Primary -DsIntegration DsIntegrated -LookupType Forward
 
            Gets all primary, Active Directory integrated, forward lookup zones in the current user forest.
 
        .INPUTS
            System.String, System.String, System.String, System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            PSObject[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$Forest = [System.String]::Empty,
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [ValidateSet("All","Primary","Secondary","Stub")]
        [string]$ZoneType = "All",
        [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)]
        [ValidateSet("All","DsIntegrated","NonDsIntegrated")]
        [string]$DsIntegration = "All",
        [Parameter(Position=3,ValueFromPipelineByPropertyName=$true)]
        [ValidateSet("All","Forward","Reverse")]
        [string]$LookupType = "All",
        [Parameter(Position=4,ValueFromPipelineByPropertyName=$true)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
    )

    Begin {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }
    }

    Process
    {
        $Zones = @()

        Get-ForestDNSServers -Forest $Forest -Credential $Credential | ForEach-Object {
            $Zones += Get-SpecificServerDnsZones -ComputerName $_ -ZoneType $ZoneType -DsIntegration $DsIntegration -LookupType $LookupType | Where-Object {-not $_.ZoneName.StartsWith("_")}
        }
    }
    
    End
    {
        $Zones | Group-Object -Property ZoneName | Select-Object -Property Group | ForEach-Object {
            Write-Output ($_.Group | Select-Object -First 1)
        }
    }
}

Function Get-SpecificServerDnsZones {
    <#
        .SYNOPSIS
            Get all of the forward lookup zones in a forest.
 
        .DESCRIPTION
            The Get-ForestDnsZones cmdlet gets all of the primary forward lookup zones for a forest.
 
        .PARAMETER Forest
            The DNS Forest to search.
 
        .PARAMETER ZoneType
            Select from All, Primary, Seconday, or Stub. Defaults to All.
 
        .PARAMETER DsIntegration
            Select from All, DsIntegrated, or NonDsIntegrated. Defaults to All.
 
        .PARAMETER LookupType
            Select from All, Forward, or Reverse. Defaults to All.
 
        .EXAMPLE
            PS C:\>Get-SpecificServerDnsZones
 
            Gets all of the dns zones on the localhost.
 
        .EXAMPLE
            PS C:\>Get-SpecificServerDnsZones -ComputerName server01
 
            Gets all of the dns zones on server01.
 
        .EXAMPLE
            PS C:\>Get-SpecificServerDnsZones -ComputerName server01 -ZoneType Primary -DsIntegration DsIntegrated -LookupType Forward
 
            Gets all primary, Active Directory integrated, forward lookup zones on server01.
 
        .INPUTS
            System.String, System.String, System.String, System.String
 
        .OUTPUTS
            PSObject[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>


    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$ComputerName ="localhost",
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [ValidateSet("All","Primary","Secondary","Stub")]
        [string]$ZoneType = "All",
        [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)]
        [ValidateSet("All","DsIntegrated","NonDsIntegrated")]
        [string]$DsIntegration = "All",
        [Parameter(Position=3,ValueFromPipelineByPropertyName=$true)]
        [ValidateSet("All","Forward","Reverse")]
        [string]$LookupType = "All"
    )

    Begin {}

    Process
    {
        switch (($ZoneType + $DsIntegration + $LookupType))
        {
            default {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue)}
            "AllAllAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue)}
            "AllAllForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.IsReverseLookupzone -eq $false})}
            "AllAllReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.IsReverseLookupzone -eq $true})}
            "AllDsIntegratedAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.IsDsIntegrated -eq $true})}
            "AllDsIntegratedForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.IsDsIntegrated -eq $true -and $_.IsReverseLookupZone -eq $false})}
            "AllDsIntegratedReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.IsDsIntegrated -eq $true -and $_.IsReverseLookupzone -eq $true})}
            "AllNonDsIntegratedAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.IsDsIntegrated -eq $false})}
            "AllNonDsIntegratedForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.IsDsIntegrated -eq $false -and $_.IsReverseLookupZone -eq $false})}
            "AllNonDsIntegratedReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.IsDsIntegrated -eq $false -and $_.IsReverseLookupzone -eq $true})}

            "PrimaryAllAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Primary"})}
            "PrimaryAllForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Primary" -and $_.IsReverseLookup -eq $false})}
            "PrimaryAllReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Primary" -and $_.IsReverseLookup -eq $true})}
            "PrimaryDsIntegratedAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Primary" -and $_.IsDsIntegrated -eq $true})}
            "PrimaryDsIntegratedForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Primary" -and $_.IsDsIntegrated -eq $true -and $_.IsReverseLookupZone -eq $false})}
            "PrimaryDsIntegratedReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Primary" -and $_.IsDsIntegrated -eq $true -and $_.IsReverseLookupZone -eq $true})}
            "PrimaryNonDsIntegratedAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Primary" -and $_.IsDsIntegrated -eq $false})}
            "PrimaryNonDsIntegratedForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Primary" -and $_.IsDsIntegrated -eq $false -and $_.IsReverseLookupZone -eq $false})}
            "PrimaryNonDsIntegratedReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Primary" -and $_.IsDsIntegrated -eq $true -and $_.IsReverseLookupZone -eq $true})}

            "SecondaryAllAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Secondary"})}
            "SecondaryAllForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Secondary" -and $_.IsReverseLookup -eq $false})}
            "SecondaryAllReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Secondary" -and $_.IsReverseLookup -eq $true})}
            "SecondaryDsIntegratedAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Secondary" -and $_.IsDsIntegrated -eq $true})}
            "SecondaryDsIntegratedForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Secondary" -and $_.IsDsIntegrated -eq $true -and $_.IsReverseLookupZone -eq $false})}
            "SecondaryDsIntegratedReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Secondary" -and $_.IsDsIntegrated -eq $true -and $_.IsReverseLookupZone -eq $true})}
            "SecondaryNonDsIntegratedAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Secondary" -and $_.IsDsIntegrated -eq $false})}
            "SecondaryNonDsIntegratedForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Secondary" -and $_.IsDsIntegrated -eq $false -and $_.IsReverseLookupZone -eq $false})}
            "SecondaryNonDsIntegratedReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Secondary" -and $_.IsDsIntegrated -eq $true -and $_.IsReverseLookupZone -eq $true})}

            "StubAllAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Stub"})}
            "StubAllForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Stub" -and $_.IsReverseLookup -eq $false})}
            "StubAllReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Stub" -and $_.IsReverseLookup -eq $true})}
            "StubDsIntegratedAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Stub" -and $_.IsDsIntegrated -eq $true})}
            "StubDsIntegratedForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Stub" -and $_.IsDsIntegrated -eq $true -and $_.IsReverseLookupZone -eq $false})}
            "StubDsIntegratedReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Stub" -and $_.IsDsIntegrated -eq $true -and $_.IsReverseLookupZone -eq $true})}
            "StubNonDsIntegratedAll" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Stub" -and $_.IsDsIntegrated -eq $false})}
            "StubNonDsIntegratedForward" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Stub" -and $_.IsDsIntegrated -eq $false -and $_.IsReverseLookupZone -eq $false})}
            "StubNonDsIntegratedReverse" {Write-Output (Get-DnsServerZone -ComputerName $ComputerName -WarningAction SilentlyContinue | Where-Object {$_.ZoneType -eq "Stub" -and $_.IsDsIntegrated -eq $true -and $_.IsReverseLookupZone -eq $true})}
        }
    }
    
    End {}
}

Function Set-ForestDnsServerScavenging {
    <#
        .SYNOPSIS
            Sets DNS record scavenging for all zones in the Forest.
 
        .DESCRIPTION
            Creates the File Access Rules for EventLog, System, and Administrators.
 
        .PARAMETER RefreshInterval
            Specifies the refresh interval as a TimeSpan object. During this interval, a DNS server can refresh a resource record that has a non-zero time stamp. Zones on the server inherit this value automatically.
             
            If a DNS server does not refresh a resource record that has a non-zero time stamp, the DNS server can remove that record during the next scavenging.
 
            Do not select a value smaller than the longest refresh period of a resource record registered in the zone.
 
            The minimum value is 0. The maximum value is 8760 hours (seven days). The default value is 7 days.
 
        .PARAMETER ScavengingInterval
            Specifies a length of time as a TimeSpan object. ScavengingInterval determines whether the scavenging feature for the DNS server is enabled and sets the number of hours between scavenging cycles.
 
            The default setting is 7. A setting greater than 0 enables scavenging for the server and sets the number of days, hours, minutes, and seconds (formatted as dd.hh:mm:ss) between scavenging cycles. The minimum value is 0. The maximum value is 365.00:00:00 (1 year).
 
        .EXAMPLE
            PS C:\>Set-ForestDnsServerScavenging
 
            Enables stale record scavenging
 
        .INPUTS
            System.TimeSpan, System.TimeSpan, System.Management.Automation.SwitchParameter
 
        .OUTPUTS
            Microsoft.Management.Infrastructure.CimInstance#DnsServerScavenging[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipelineByPropertyName=$true)]
        [System.TimeSpan]$RefreshInterval = [System.TimeSpan]::FromDays(7),
        [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [System.TimeSpan]$ScavengingInterval = [System.TimeSpan]::FromDays(7),
        [Parameter(Position=2)]
        [switch]$PassThru = $false
    )
    Begin {
        $Forest = Get-ADForest

        Write-Host "Setting DNS Server Scavenging."
    }

    Process {
        foreach ($Domain in $Forest.Domains)
        {
            Write-Host $Domain
                
            $Context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain, $Domain)
            $Servers = [System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($Context)
            
            foreach ($Server in $Servers)
            {
                Write-Host $Server.Name

                try {
                    $Scavenging = Set-DnsServerScavenging -ScavengingState $true -ApplyOnAllZones -ComputerName $Server.Name -RefreshInterval ([System.TimeSpan]::FromDays(7)) -ScavengingInterval ([System.TimeSpan]::FromDays(7)) -PassThru

                    if ($PassThru) {
                        Write-Output $Scavenging
                    }
                }
                catch [Exception] {
                    Write-Warning $_.Exception.Message
                }
            }
        } 
    }
    
    End {

    }
}

Function New-EventLogAccessRuleSet {
    <#
        .SYNOPSIS
            Creates the File Access Rules for EventLog, System, and Administrators.
 
        .DESCRIPTION
            Creates the File Access Rules for EventLog, System, and Administrators.
 
        .EXAMPLE
            PS C:\>New-EventLogAccessRuleSet
 
            Creates the rule set.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.Security.AccessControl.FileSystemAccessRule[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>

    [CmdletBinding()]
    Param(

    )

    Begin {

    }

    Process {
        # NT Service\EventLog = S-1-5-80-880578595-1860270145-482643319-2788375705-1540778122
        [System.Security.Principal.SecurityIdentifier]$NTServiceEventLogSid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-80-880578595-1860270145-482643319-2788375705-1540778122")
        $Administrators = New-Object Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null)
        $EventLog = New-Object Security.Principal.SecurityIdentifier($NTServiceEventLogSid)
        $System = New-Object  Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null)

        $AdministratorAce = New-Object System.Security.AccessControl.FileSystemAccessRule($Administrators, 
            [System.Security.AccessControl.FileSystemRights]::FullControl, 
            @([System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow)

        $EventLogAce = New-Object System.Security.AccessControl.FileSystemAccessRule($EventLog, 
            [System.Security.AccessControl.FileSystemRights]::FullControl, 
            @([System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow)

        $SystemAce = New-Object System.Security.AccessControl.FileSystemAccessRule($System, 
            [System.Security.AccessControl.FileSystemRights]::FullControl, 
            @([System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow)


        [System.Security.AccessControl.FileSystemAccessRule[]]$Rules = @($AdministratorAce, $EventLogAce, $SystemAce)
    }

    End {
        Write-Output $Rules
    }
}

Function New-CryptoFolderAccessRuleSet {
    <#
        .SYNOPSIS
            Creates the File Access Rules for System and Administrators.
 
        .DESCRIPTION
            Creates the File Access Rules for System and Administrators.
 
        .EXAMPLE
            PS C:\>New-CryptoFolderAccessRuleSet
 
            Creates the rule set.
        .INPUTS
            None
 
        .OUTPUTS
            System.Security.AccessControl.FileSystemAccessRule[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>

    [CmdletBinding()]
    Param()

    Begin {

    }

    Process {
        [System.Security.Principal.SecurityIdentifier]$NTServiceEventLogSid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-80-880578595-1860270145-482643319-2788375705-1540778122")
        $Administrators = New-Object Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null)
        $System = New-Object  Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null)

        $AdministratorAce = New-Object System.Security.AccessControl.FileSystemAccessRule($Administrators, 
            [System.Security.AccessControl.FileSystemRights]::FullControl, 
            @([System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow)

        $SystemAce = New-Object System.Security.AccessControl.FileSystemAccessRule($System, 
            [System.Security.AccessControl.FileSystemRights]::FullControl, 
            @([System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit),
            [System.Security.AccessControl.PropagationFlags]::None,
            [System.Security.AccessControl.AccessControlType]::Allow)


        [System.Security.AccessControl.FileSystemAccessRule[]] $Rules = @($AdministratorAce, $SystemAce)
    }

    End {
        Write-Output $Rules
    }
}

Function Set-FilePermissions {
    <#
        .SYNOPSIS
            Sets permissions on a file or directory.
 
        .DESCRIPTION
            Will set permissions on file or directory with the provided rule set.
 
        .PARAMETER Path
            The path to the file to set permissions on.
 
        .PARAMETER Rules
            An array of File Access Rules to apply to the path.
 
        .PARAMETER Replace
            Indictates if all permissions on the path should be replaced with these. Otherwise the specified access rules will just be added to the target.
 
        .PARAMETER ForceInheritance
            Indicates if all permissions of child items should have their permissions replaced with these if the target is a directory.
 
        .EXAMPLE
            PS C:\>Set-Permissions -Path "c:\test.txt" -ComputerName -Rules $Rules
 
            Creates the rule set on the test.txt file.
 
        .INPUTS
            System.String, System.Security.AccessControl.FileSystemAccessRule[], System.Management.Automation.SwitchParameter, System.Management.Automation.SwitchParameter
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>


    Param 
    (
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeLineByPropertyName=$true)]
        [string]$Path,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeLineByPropertyName=$true)]
        [System.Security.AccessControl.FileSystemAccessRule[]]$Rules,
        [Parameter(Position=2,ValueFromPipeLineByPropertyName=$true)]
        [switch]$Replace = $false,
        [Parameter(Position=3,ValueFromPipeLineByPropertyName=$true)]
        [switch]$ForceInheritance = $false
    )

    Begin 
    {
        Write-Host "Setting permissions on $Path"
    }

    Process
    {
        try
        {
            $Acl = Get-Acl -Path $Path

            if ($Acl -ne $null)
            {
                #Should the permissions be replaced
                if($Replace)
                {
                    $OldAcls = $Acl.Access

                    foreach ($Rule in $OldAcls)
                    {
                        $Acl.RemoveAccessRule($Rule) | Out-Null
                    }
                }
                #Only remove the permissions for principals we're updating
                else
                {
                    $OldAcls = $Acl.Access | Where-Object {$Rules.IdentityReference -eq  $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier])}

                    foreach ($Rule in $OldAcls)
                    {
                        $Acl.RemoveAccessRule($Rule) | Out-Null
                    }
                }

                foreach ($Rule in $Rules)
                {
                    $Acl.AddAccessRule($Rule) | Out-Null
                }

                Set-Acl -Path $Path -AclObject $Acl

                #If child permissions should be forced to inherit
                if ($ForceInheritance)
                {
                    Get-ChildItem -Path $Path -Recurse -Force | ForEach-Object {

                        $ChildAcl = Get-Acl -Path $_.FullName 
                        $ChildPath = $_.FullName

                        Write-Host "Forcing inheritance on $ChildPath"

                        foreach ($ChildRule in $ChildAcl.Access)
                        {
                            try
                            {
                                $ChildAcl.RemoveAccessRule($ChildRule) | Out-Null
                            }
                            catch [Exception]
                            {
                                Write-Warning "Error removing ACL from $ChildPath`: $($_.ToString())"
                            }
                        }

                        $ChildAcl.SetAccessRuleProtection($false,$false)

                        Set-Acl -Path $_.FullName -AclObject $ChildAcl | Out-Null
                    }
                }
                    
                Write-Host $env:COMPUTERNAME -ForegroundColor DarkRed -BackgroundColor White
                Write-Host $Path -ForegroundColor DarkRed -BackgroundColor White
                $Acl.Access
            }
            else
            {
                Write-Warning "Could not retrieve the ACL for $Path"
            }
        }
        catch [System.Exception]
        {
            Write-Warning $_.Exception.Message
        }
    }
    
    End {}
}

Function Set-Owner {
    <#
        .SYNOPSIS
            Changes owner of a file or folder to another user or group.
 
        .DESCRIPTION
            Changes owner of a file or folder to another user or group.
 
        .PARAMETER Path
            The folder or file that will have the owner changed.
 
        .PARAMETER Account
            Optional parameter to change owner of a file or folder to specified account.
 
            Default value is 'Builtin\Administrators'
 
        .PARAMETER Recurse
            Recursively set ownership on subfolders and files beneath given folder.
 
        .EXAMPLE
            PS C:\>Set-Owner -Path C:\temp\test.txt
 
            Changes the owner of test.txt to Builtin\Administrators
 
        .EXAMPLE
            PS C:\>Set-Owner -Path C:\temp\test.txt -Account 'Domain\bprox
 
            Changes the owner of test.txt to Domain\bprox
 
        .EXAMPLE
            PS C:\>Set-Owner -Path C:\temp -Recurse
 
            Changes the owner of all files and folders under C:\Temp to Builtin\Administrators
 
        .EXAMPLE
            PS C:\>Get-ChildItem C:\Temp | Set-Owner -Recurse -Account 'Domain\Administrator'
 
            Changes the owner of all files and folders under C:\Temp to Domain\Administrator
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    Param (
        [parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [Alias("FullName")]
        [string[]]$Path,
        [parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
        [string]$Account = 'BUILTIN\Administrators',
        [parameter(Position=2,ValueFromPipelineByPropertyName=$true)]
        [switch]$Recurse
    )
    Begin {
        #Prevent Confirmation on each Write-Debug command when using -Debug
        If ($PSBoundParameters['Debug']) {
            $DebugPreference = 'Continue'
        }
        Try {
            [void][TokenAdjuster]
        } Catch {
            $AdjustTokenPrivileges = @"
            using System;
            using System.Runtime.InteropServices;
 
             public class TokenAdjuster
             {
              [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
              internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
              ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
              [DllImport("kernel32.dll", ExactSpelling = true)]
              internal static extern IntPtr GetCurrentProcess();
              [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
              internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr
              phtok);
              [DllImport("advapi32.dll", SetLastError = true)]
              internal static extern bool LookupPrivilegeValue(string host, string name,
              ref long pluid);
              [StructLayout(LayoutKind.Sequential, Pack = 1)]
              internal struct TokPriv1Luid
              {
               public int Count;
               public long Luid;
               public int Attr;
              }
              internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
              internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
              internal const int TOKEN_QUERY = 0x00000008;
              internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
              public static bool AddPrivilege(string privilege)
              {
               try
               {
                bool retVal;
                TokPriv1Luid tp;
                IntPtr hproc = GetCurrentProcess();
                IntPtr htok = IntPtr.Zero;
                retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
                tp.Count = 1;
                tp.Luid = 0;
                tp.Attr = SE_PRIVILEGE_ENABLED;
                retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
                retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
                return retVal;
               }
               catch (Exception ex)
               {
                throw ex;
               }
              }
              public static bool RemovePrivilege(string privilege)
              {
               try
               {
                bool retVal;
                TokPriv1Luid tp;
                IntPtr hproc = GetCurrentProcess();
                IntPtr htok = IntPtr.Zero;
                retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
                tp.Count = 1;
                tp.Luid = 0;
                tp.Attr = SE_PRIVILEGE_DISABLED;
                retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
                retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
                return retVal;
               }
               catch (Exception ex)
               {
                throw ex;
               }
              }
             }
"@

            Add-Type $AdjustTokenPrivileges
        }

        #Activate necessary admin privileges to make changes without NTFS perms
        [void][TokenAdjuster]::AddPrivilege("SeRestorePrivilege") #Necessary to set Owner Permissions
        [void][TokenAdjuster]::AddPrivilege("SeBackupPrivilege") #Necessary to bypass Traverse Checking
        [void][TokenAdjuster]::AddPrivilege("SeTakeOwnershipPrivilege") #Necessary to override FilePermissions
    }
    Process {
        ForEach ($Item in $Path) {
            Write-Verbose "FullName: $Item"
            #The ACL objects do not like being used more than once, so re-create them on the Process block
            $DirOwner = New-Object System.Security.AccessControl.DirectorySecurity
            $DirOwner.SetOwner([System.Security.Principal.NTAccount]$Account)
            $FileOwner = New-Object System.Security.AccessControl.FileSecurity
            $FileOwner.SetOwner([System.Security.Principal.NTAccount]$Account)
            $DirAdminAcl = New-Object System.Security.AccessControl.DirectorySecurity
            $FileAdminAcl = New-Object System.Security.AccessControl.DirectorySecurity
            $AdminACL = New-Object System.Security.AccessControl.FileSystemAccessRule('Builtin\Administrators','FullControl','ContainerInherit,ObjectInherit','InheritOnly','Allow')
            $FileAdminAcl.AddAccessRule($AdminACL)
            $DirAdminAcl.AddAccessRule($AdminACL)
            Try {
                $Item = Get-Item -LiteralPath $Item -Force -ErrorAction Stop
                If (-NOT $Item.PSIsContainer) {
                    If ($PSCmdlet.ShouldProcess($Item, 'Set File Owner')) {
                        Try {
                            $Item.SetAccessControl($FileOwner)
                        } Catch {
                            Write-Warning "Couldn't take ownership of $($Item.FullName)! Taking FullControl of $($Item.Directory.FullName)"
                            $Item.Directory.SetAccessControl($FileAdminAcl)
                            $Item.SetAccessControl($FileOwner)
                        }
                    }
                } Else {
                    If ($PSCmdlet.ShouldProcess($Item, 'Set Directory Owner')) {                        
                        Try {
                            $Item.SetAccessControl($DirOwner)
                        } Catch {
                            Write-Warning "Couldn't take ownership of $($Item.FullName)! Taking FullControl of $($Item.Parent.FullName)"
                            $Item.Parent.SetAccessControl($DirAdminAcl) 
                            $Item.SetAccessControl($DirOwner)
                        }
                    }
                    If ($Recurse) {
                        [void]$PSBoundParameters.Remove('Path')
                        Get-ChildItem $Item -Force | Set-Owner @PSBoundParameters
                    }
                }
            } Catch {
                Write-Warning "$($Item): $($_.Exception.Message)"
            }
        }
    }
    End {  
        #Remove priviledges that had been granted
        [void][TokenAdjuster]::RemovePrivilege("SeRestorePrivilege") 
        [void][TokenAdjuster]::RemovePrivilege("SeBackupPrivilege") 
        [void][TokenAdjuster]::RemovePrivilege("SeTakeOwnershipPrivilege")     
    }
}

Function Test-RegistryEntry {
    <#
        .SYNOPSIS
            Tests the existence of a registry value
 
        .DESCRIPTION
            The Test-RegistryEntry cmdlet test the extistence of a registry value (property of a key).
 
        .PARAMETER Key
            The registry key to test for containing the property.
 
        .PARAMETER PropertyName
            The property name to test for.
 
        .EXAMPLE
            PS C:\>Test-RegistryEntry -Key "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing" -PropertyName PendingFileRenameOperations
            Returns true or false depending on the existence of the property
 
        .INPUTS
            System.String, System.String
 
        .OUTPUTS
            System.Boolean
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>


    Param (
        [Parameter(Position=0, ValueFromPipelineByPropertyName=$true, Mandatory=$true)]
        [string]$Key,
        [Parameter(Position=1, ValueFromPipelineByPropertyName=$true, Mandatory=$true)]
        [string]$PropertyName
    )

    Get-ItemProperty -Path $Key -Name $PropertyName -ErrorAction SilentlyContinue | Out-Null
    return $?
}

Function Test-IsEnterpriseAdmin {
    <#
        .SYNOPSIS
            Tests if a user is a member of the Enterprise Admins group.
 
        .DESCRIPTION
            The Test-IsEnterpriseAdmin returns true if the user is in the group and false otherwise.
 
        .EXAMPLE
            Test-IsEnterpriseAdmin
 
            Determines if the user credentials being used to run the cmdlet have Enterprise Admin privileges.
         
        .EXAMPLE
            Test-IsEnterpriseAdmin -UserName "John Smith"
 
            Determines if the user John Smith has Enterprise Admin privileges.
 
        .EXAMPLE
            Test-IsEnterpriseAdmin -Credential (Get-Credential)
             
            Determines if the entered user credentials have Enterprise Admin privileges.
 
        .PARAMETER UserName
            The user to test the group membership on. If no user name is specified, the cmdlet runs against WindowsIdentity Principal.
 
        .PARAMETER Credential
            The PSCredential to use to test if the user has Enterprise Admin credentials.
 
        .INPUTS
            System.Management.Automation.PSCredential, System.String
 
            System.String, System.String
 
        .OUTPUTS
            System.Boolean
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/24/2016
    #>


    [CmdletBinding(DefaultParameterSetName="Username")]
    Param (
        [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true,ParameterSetName="Username")]
        [string]$UserName = [System.String]::Empty,
        [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true,ParameterSetName="Credential")]
        [PSCredential]$Credential = [PSCredential]::Empty
    )

    Begin {
        Import-Module ActiveDirectory -ErrorAction Stop

        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }

        [bool]$IsAdmin = $false
    }

    Process 
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            "Username" {
                if ($UserName -ne [System.String]::Empty)
                {
                    $CurrentUser = $UserName
                }
                else
                {
                    $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent()
                    if ($Principal.IsSystem) {
                        Write-Host "Current principal is the SYSTEM account."
                        $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
                        if ($Role -eq 2) {
                            Write-Host "Current principal is a domain controller."
                            $IsAdmin = $true
                        }
                        else {
                            Write-Warning "Current principal is the SYSTEM account, but not a domain controller."
                            $CurrentUser = $Principal.Name
                        }            
                    }
                    else {
                        $CurrentUser = $Principal.Name
                    }
                }
            }
            "Credential" {
                if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
                    $CurrentUser = $Credential.UserName
                }
                else {
                    $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent()
                    if ($Principal.IsSystem) {
                        Write-Host "Current principal is the SYSTEM account."
                        $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType
                        if ($Role -eq 2) {
                            Write-Host "Current principal is a domain controller."
                            $IsAdmin = $true
                        }
                        else {
                            Write-Warning "Current principal is the SYSTEM account, but not a domain controller."
                            $CurrentUser = $Principal.Name
                        }                
                    }
                    else {
                        $CurrentUser = $Principal.Name
                    }
                }
            }
            default {
                throw "Could not determine parameter set name for Test-IsEnterpriseAdmin."
            }
        }

        if(!$IsAdmin) {
            if ($CurrentUser.IndexOf("\") -ne -1) {
                $Domain = $CurrentUser.Substring(0, $CurrentUser.IndexOf("\"))
                $Forest = (Get-ADDomain -Identity $Domain).Forest
                $CurrentUser = $CurrentUser.Substring($CurrentUser.IndexOf("\") + 1)
            }
            elseif ($CurrentUser.IndexOf("@") -ne -1) {
                $Domain = $CurrentUser.Substring($CurrentUser.IndexOf("@") + 1)
                $Forest = (Get-ADDomain -Identity $Domain).Forest
                $CurrentUser = $CurrentUser.Substring(0, $CurrentUser.IndexOf("@"))
            }
            else {
                $Forest = (Get-ADForest -Current LoggedOnUser).Name
            }

            $Groups = Get-NestedGroupMembership -Principal $CurrentUser | Select-Object -Property Name,SID

            if($Credential -ne $null -and $Credential -ne [System.Management.Automation.PSCredential]::Empty) {
                $RootDomainSID = Get-ADDomain -Identity $Forest -Credential $Credential | Select-Object -ExpandProperty DomainSID
            }
            else {
                $RootDomainSID = Get-ADDomain -Identity $Forest | Select-Object -ExpandProperty DomainSID
            }
        
            [Security.Principal.SecurityIdentifier]$EnterpriseAdminSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountEnterpriseAdminsSid, $RootDomainSID)
    
            foreach ($Group in $Groups)
            {
                if ($Group.SID -eq $EnterpriseAdminSID) 
                {
                    $IsAdmin = $true
                    break
                } 
            }
        }
    }

    End {
        Write-Output $IsAdmin
    }
}

Function Get-NestedGroupMembership {
    <#
        .SYNOPSIS
            Recursively gets the group membership of an AD principal.
 
        .DESCRIPTION
            The Get-NestedGroupMembership gets all nested group membership of an AD principal.
 
        .EXAMPLE
            Get-NestedGroupMembership -Principal Administrator
 
            Gets all group membership for the Administrator account.
 
        .PARAMETER Principal
            The principal to get group membership for.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            Microsoft.ActiveDirectory.Management.ADGroup[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/6/2016
    #>

    Param(
        [Parameter(ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true,Position=0)]
        [string]$Principal,
        [Parameter(DontShow=$true)]
        [Microsoft.ActiveDirectory.Management.ADGroup[]]$Groups
    )

    Begin {
        Import-Module ActiveDirectory -ErrorAction Stop

        if ($Groups -eq $null) {
            $Groups = @()
        }

        if ([System.String]::IsNullOrEmpty($Principal)) {
            $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
        }

        if ($Principal.IndexOf("DC=") -ne -1) {
            #Pulls the DC=...,DC=...,DC=... info and turns it into an FQDN
            $Server = $Principal.Substring($Principal.IndexOf("DC=")).Replace("DC=","").Replace(",",".")
        }
        else {
            if ($Principal.IndexOf("\") -ne -1) {
                $Domain = $Principal.Substring(0, $Principal.IndexOf("\"))        
                $Server = (Get-ADDomain -Identity $Domain).DnsRoot        
                $Principal = $Principal.Substring($Principal.IndexOf("\") + 1)
            }
            elseif ($Principal.IndexOf("@") -ne -1) {
                $Domain = $Principal.Substring($Principal.IndexOf("@") + 1)
                $Server = (Get-ADDomain -Identity $Domain).DnsRoot
                $Principal = $Principal.Substring(0, $Principal.IndexOf("@"))
            }
            else {
                $Server = (Get-ADDomain -Current LoggedOnUser).DnsRoot
            }        
        }

        if ([System.String]::IsNullOrEmpty($Server)) {
            throw "Could not find a domain controller."
        }
    }

    Process {
        #Get the group membership of the evaluated principal
        $TempGroups = Get-ADPrincipalGroupMembership -Identity $Principal -Server $Server
        $GroupsToCheck = @()

        #Iterate through these groups, need to check if the Groups array already contains the group
        #If it doesn't, add the group since it is a newly discovered nested group, and also add it to the
        #array of groups to check for further nested group membership
        #We don't want to check the Groups array for nested membership since a lot of those groups have already been checked
        foreach($Group in $TempGroups) {
            if (!$Groups.Contains($Group)) {
                $Groups += $Group
                $GroupsToCheck += $Group
            }
        }

        #This array will hold newly discovered nested groups of the groups we need to check
        $NewGroups = @()

        #Get the nested group membership of each new group to check
        foreach ($Group in $GroupsToCheck) {
            $NewGroups += Get-NestedGroupMembership -Principal $Group.DistinguishedName -Groups $Groups
        }

        #After getting the nested groups, check to see if that group may have been added already to Groups
        #through nested membership in some other group that was already checked
        foreach ($Group in $NewGroups) {
            if (!$Groups.Contains($Group)) {
                $Groups += $Group
            }
        }
    }

    End {
        #Return the updated total group membership
        Write-Output $Groups
    }
}