FindDHCPServers.ps1


Description

@GitHub Copilot - Welcome @BanterBoy, I’m your Copilot and I’m here to help you get things done faster. I can identify issues, explain and even improve code.

You can ask generic questions, but what I’m really good at is helping you with your code. For example:

Generate unit tests for my code Explain the selected code Propose a fix for the bugs in my code If you want to learn more about my capabilities and limitations, check out the Copilot documentation.

I’m powered by AI, so surprises and mistakes are possible. Make sure to verify any generated code or suggestions, and share feedback so that we can learn and improve.

@BanterBoy: - Explain what the script does and why it is useful.

@GitHub Copilot -


Script

<#
  This script will find all Windows servers with the DHCP Server service installed,
  and report on it's state, if a local database (DHCP.mdb) exists, and if it's
  authorized in Active Directory.

  Note that for servers we filter out Cluster Name Objects (CNOs) and
  Virtual Computer Objects (VCOs) by checking the objects serviceprincipalname
  property for a value of MSClusterVirtualServer. The CNO is the cluster
  name, whereas a VCO is the client access point for the clustered role.
  These are not actual computers, so we exlude them to assist with
  accuracy.

  Thanks to Michael B Smith for the Get-AuthorizedDHCPServers function.

  There are multiple ways of getting information on a remote service:
  - Get-Service with the $ComputerName parameter (RPC)
  - Get-WMIObject with a $ComputerName parameter (WMI)
  - Invoke-Command to execute Get-Service remotely (WinRM/WSMAN)
  - Get-CimInstance with the $ComputerName parameter (WinRM/WSMAN)

  The Get-CimInstance is only available from v3.

  I have found that using WinRM is hit and miss, as it's not always correctly
  setup across a server fleet, and therefore you receive the following errors:

  - Get-CimInstance : WinRM cannot complete the operation. Verify that the specified
    computer name is valid, that the computer is accessible over the network, and
    that a firewall exception for the WinRM service is enabled and allows access from
    this computer. By default, the WinRM firewall exception for public profiles
    limits access to remote computers within the same local subnet.
  - New-PSSession : [<computername>] Connecting to remote server <computername> failed
    with the following error message : WinRM cannot complete the operation. Verify that
    the specified computer name is valid, that the computer is accessible over the
    network, and that a firewall exception for the WinRM service is enabled and allows
    access from this computer. By default, the WinRM firewall exception for public
    profiles limits access to remote computers within the same local subnet. For more
    information, see the about_Remote_Troubleshooting Help topic.

  Ironically I get more reliability using the Get-Service or Get-WMIObject cmdlets.

  Getting things to work correctly using WinRM/WSMAN requires some cleverness:
  - http://jdhitsolutions.com/blog/2013/04/get-ciminstance-from-powershell-2-0/
  - http://richardspowershellblog.wordpress.com/2012/01/14/cim-cmdlets-and-remote-machines/
  - http://blogs.technet.com/b/josebda/archive/2010/04/02/comparing-rpc-wmi-and-winrm-for-remote-server-management-with-powershell-v2.aspx

  Script Name: FindDHCPServers.ps1
  Release 1.2
  Written by [email protected] 15/01/2014
  Modified by [email protected] 26/03/2014

#>

#-------------------------------------------------------------
# Get the script path
$ScriptPath = { Split-Path $MyInvocation.ScriptName }
$OnlineFile = $(&$ScriptPath) + "\DHCPServers-online.csv"
$OfflineFile = $(&$ScriptPath) + "\DHCPServers-offline.csv"

#-------------------------------------------------------------

Import-Module ActiveDirectory

# Get Domain Controllers
#$Computers = Get-ADDomainController -Filter * | Sort-Object Name

# Get All Servers filtering out Cluster Name Objects (CNOs) and Virtual computer Objects (VCOs)
$Computers = Get-ADComputer -Filter * -Properties Name, Operatingsystem, servicePrincipalName | Where-Object { ($_.Operatingsystem -like '*server*') -AND !($_.serviceprincipalname -like '*MSClusterVirtualServer*') } | Sort-Object Name

$Filter = "(&(objectCategory=computer)(operatingSystem=*server*)(!(servicePrincipalName=*MSClusterVirtualServer*)))"

# Get All Authorized DHCP Servers
# This has been purposely commented out as I am now using the
# Get-AuthorizedDHCPServers function.
#$defaultNC = ([ADSI]"LDAP://RootDSE").defaultNamingContext.Value
#$configurationNC = "cn=configuration," + $defaultNC
#$AuthorizedDHCPServers = Get-ADObject -SearchBase "CN=NetServices,CN=Services,$configurationNC" -Filter "objectclass -eq 'dhcpclass' -AND Name -ne 'dhcproot'"

#-------------------------------------------------------------
function Get-AuthorizedDHCPServers {
    # The configNC is replicated to all DCs in a forest, similar
    # to the schema, so this function will get all authorized
    # DHCP servers in the forest.
    $adsi = [ADSI]( "LDAP://RootDSE" )
    $configNC = $adsi.configurationNamingContext.Item( 0 )
    $names = @()
    $cn = [ADSI]("LDAP://CN=NetServices,CN=Services," + $configNC )
    foreach ( $object in $cn.children ) {
        if ( $null -ne $object.Properties[ 'dhcpIdentification' ] ) {
            if ( $object.dhcpIdentification -eq 'DHCP Server Object' ) {
                $obj = New-Object PSObject
                $obj | Add-Member NoteProperty -Name "DistinguishedName" $object.distinguishedName[0]
                $obj | Add-Member NoteProperty -Name "Name" $object.Name[0]
                $names += $obj
            }
        }
    }
    $names
}

# Get All Authorized DHCP Servers
$AuthorizedDHCPServers = Get-AuthorizedDHCPServers

#-------------------------------------------------------------

$onlinearray = @()
$offlinearray = @()

ForEach ($Computer in $Computers) {
    $ComputerError = "$false"
    #$ComputerName = $Computer.Name
    $ComputerName = $Computer.DNSHostName
    if (Test-Connection -Cn $ComputerName -BufferSize 16 -Count 1 -ea 0 -quiet) {
        write-Host -ForegroundColor Green "Checking for DHCP Server service on $ComputerName"
        $ServiceName = "DHCPServer"
        Try {
            # If using v3, we can use Get-CimInstance. otherwise we need to use Get-Service.
            If ($PSVersionTable.PSVersion.Major -eq 3) {
                $serviceObj = Get-CimInstance Win32_Service -Computer $ComputerName | Where-Object { $_.Name -eq $serviceName } | Select-Object Name, State
            }
            else {
                #$sessions = New-PSSession -ComputerName $ComputerName
                #$serviceObj = Invoke-Command -Session $sessions -ScriptBlock {Get-Service | ?{ $_.ServiceName -eq $serviceName } | Select-Object -Property Name, @{Name="State";Expression={$_.Status}}}
                #Remove-PSSession $sessions
                $serviceObj = Get-Service -ComputerName $ComputerName | Where-Object { $_.ServiceName -eq $serviceName } | Select-Object -Property Name, @{Name = "State"; Expression = { $_.Status } }
            }
            If ($NULL -ne $serviceObj) {
                $State = $serviceObj.State
                If ($State -eq "Running") {
                    write-host -ForegroundColor green "- Service found in a $State state."
                }
                Else {
                    write-host -ForegroundColor red "- Service found in a $State state."
                }
                # Path to DHCP
                $path = "\\$ComputerName\admin$\System32\dhcp"
                # Testing the $path
                IF ((Test-Path -Path $path) -and ($null -ne (Get-Item -Path $path).Length)) {
                    IF ((Get-ChildItem "$path\Dhcp.mdb" | Measure-Object).Count -gt 0) {
                        write-host -ForegroundColor green "- Database file found."
                        $DatabaseExists = $True
                    }
                    Else {
                        write-host -ForegroundColor red "- Database file not found."
                        $DatabaseExists = $False
                    }
                }
                Else {
                    $ComputerError = "$true"
                    $ErrorDescription = "Not reachable via the $path path."
                    write-Host -ForegroundColor Red "$ErrorDescription"
                }
                $ISAuthorized = $False
                ForEach ($AuthorizedDHCPServer in $AuthorizedDHCPServers) {
                    If (($AuthorizedDHCPServer.Name).ToLower().Contains($ComputerName.ToLower())) {
                        $ISAuthorized = $True
                        write-host -ForegroundColor green "- Authorized in Active Directory."
                    }
                }
                If ($ISAuthorized -eq $False) {
                    write-host -ForegroundColor red "- Not authorized in Active Directory."
                }
                $output = New-Object PSObject
                $output | Add-Member NoteProperty -Name "ComputerName" $ComputerName
                $output | Add-Member NoteProperty -Name "Service" $serviceObj.Name
                $output | Add-Member NoteProperty -Name "State" $State
                $output | Add-Member NoteProperty -Name "DatabaseExists" $DatabaseExists
                $output | Add-Member NoteProperty -Name "ISAuthorized" $ISAuthorized
                $onlinearray += $output
            }
            Else {
                write-host -ForegroundColor yellow "- $ServiceName service not installed"
            }
        }
        Catch {
            $ComputerError = "$true"
            $ErrorDescription = "Error connecting using the Get-Service cmdlet."
            write-Host -ForegroundColor Red "- $ErrorDescription"
        }
    }
    Else {
        $ComputerError = "$true"
        $ErrorDescription = "Unable to ping server"
        write-Host -ForegroundColor Red "$ComputerName is offline"
    }
    if ($ComputerError -eq $true) {
        $outputbad = New-Object PSObject
        $outputbad | Add-Member NoteProperty ComputerName $ComputerName
        $outputbad | Add-Member NoteProperty ErrorDescription $ErrorDescription
        $offlinearray += $outputbad
    }
}
$onlinearray | export-csv -notype "$OnlineFile"
$offlinearray | export-CSV -notype "$OfflineFile"

# Remove the quotes
(get-content "$OnlineFile") | ForEach-Object { $_ -replace '"', "" } | out-file "$OnlineFile" -Force -Encoding ascii
(get-content "$OfflineFile") | ForEach-Object { $_ -replace '"', "" } | out-file "$OfflineFile" -Force -Encoding ascii

Back to Top


Download

Please feel free to copy parts of the script or if you would like to download the entire script, simple click the download button. You can download the complete repository in a zip file by clicking the Download link in the menu bar on the left hand side of the page.


Report Issues

You can report an issue or contribute to this site on GitHub. Simply click the button below and add any relevant notes. I will attempt to respond to all issues as soon as possible.

Issue


Back to Top