Наблюдаем за новыми арендами адресов на DHCP сервере с помощью PowerShell

в 9:34, , рубрики: dhcp, powershell, sqlite, windows, Серверное администрирование

Наблюдаем за новыми арендами адресов на DHCP сервере с помощью PowerShell - 1 Хочу рассказать об одном из способов мониторить новые аренды адресов на Windows DHCP сервере со множеством скопов. Задача была следующая: находить новые аренды и пробегать список глазами на предмет неугодных хостов с последующим их баном.

Для решения мы создадим базу SQLite и будем сохранять в ней информацию о существующих арендах, а затем, с помощью запроса, сравнивать таблицу, полученную ранее (например, вчера или неделю назад) с актуальными данными.

В начале хочу отметить, что я не являюсь программистом и, возможно, представленный здесь код не очень эффективен, но он решает задачу. В скрипте использованы командлеты, появившиеся впервые в PowerShell 3.0, а работа проверена на Windows Server 2012 R2, в качестве DHCP сервера выступил Windows Server 2008 R2.

Чтобы использовать SQLite, нам понадобятся две библиотеки: System.Data.SQLite.dll и SQLite.Interop.dll. Скачать их можно здесь в составе System.Data.SQLite. Нам нужна не-bundle версия. На моем компьютере установлена версия фреймворка 4.5, поэтому я выбрал sqlite-netFx45-setup-x64-2012-1.0.96.0.exe

Подключаем библиотеку, в данном случае dll находятся в одной папке со скриптом:

# Adding SQLite libraries, should be two files in script directory: System.Data.SQLite.dll, SQLite.Interop.dll
Add-Type -Path "$PSScriptRootSystem.Data.SQLite.dll" -ErrorAction Stop

Для удобства оборачиваем существующие методы в функции скрипта:

# Function to execute command non query
function ExecuteNonQuery($commandtext) {
    $SQLiteCommand.CommandText = $commandtext
    [void]$SQLiteCommand.ExecuteNonQuery()
}

# Function to execute command query
function ExecuteReader($commandtext) {
    $SQLiteCommand.CommandText = $commandtext
    $DataReader = $SQLiteCommand.ExecuteReader()
    $Data = New-Object -TypeName System.Data.DataTable
    $Data.Load($DataReader)
    $DataReader.Close()
    return $Data
}

Опрашиваем DHCP сервер на предмет актуальных аренд. Если при запуске не был указан параметр -IncludeReservations, пропускаем резервирования:

# Getting DHCP scopes and leases from DHCP server, excludes reserved addresses if -IncludeReservations not specified as parameter
$Scopes = Get-DhcpServerv4Scope -ComputerName $ComputerName
if ($IncludeReservations) {
    $Leases = foreach ($Scope in $Scopes) { Get-DhcpServerv4Lease -ScopeId $Scope.ScopeId -ComputerName $ComputerName }  
}
else {
    $Leases = foreach ($Scope in $Scopes) { Get-DhcpServerv4Lease -ScopeId $Scope.ScopeId -ComputerName $ComputerName | where AddressState -notlike "*Reservation" }
}

Создаем базу или устанавливаем соединение с уже существующей, а также создаем экземпляр объекта SQLite.SQLiteCommand:

# Creating and opening SQLite DB connection
# If DB file does not exist, create a new file. DB file name is "<name_of_server>.sqlite"
$SQLiteConnection = New-Object -TypeName System.Data.SQLite.SQLiteConnection
$DBFile = "$DBDirectory$ComputerName.sqlite"
if (Test-Path $DBFile) {
    Write-Verbose "Opening $DBFile"
    $SQLiteConnection.ConnectionString = "Data Source=$DBFile;"
}
else {
    Write-Verbose "Creating $DBFile"            
    $SQLiteConnection.ConnectionString = "Data Source=$DBFile;New=True;"
}
$SQLiteConnection.Open()

# Create SQLite command object using previously created DB connection
$SQLiteCommand = New-Object -TypeName System.Data.SQLite.SQLiteCommand
$SQLiteCommand.Connection = $SQLiteConnection

Создаем «сегодняшнюю» таблицу, с помощью запроса:

CREATE TABLE $CurrentTableName (IPAddress PRIMARY KEY, ScopeId, AddressState, ClientId, HostName);

Заполняем ее полученными с сервера значениями. Ранее определенная функция str() возвращает пустую строку вместо null:

INSERT OR IGNORE INTO $CurrentTableName VALUES('$(str($Lease.IPAddress))', '$(str($Lease.ScopeId))', '$(str($Lease.AddressState))', '$(str($Lease.ClientId))', '$(str($Lease.HostName))');

Выбираем две последние по именам (они же даты) таблицы, чтобы сравнить их содержимое:

SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name DESC LIMIT 2;

И сравниваем:

SELECT * FROM $LastTableName WHERE $LastTableName.ClientId NOT IN (SELECT ClientId FROM $PrevTableName);

Выводим результат в консоль:

$Result | ft -Wrap -AutoSize

Пример выводимой таблицы:

IPAddress      ScopeId        AddressState ClientId          HostName                                     
---------      -------        ------------ --------          --------                                     
10.10.10.22    10.10.10.0     Active       a5-b4-c3-d2-e1-f0 UsersOwnDevice.domain

Закрываем соединение с БД и уничтожаем объекты:

# Closing and disposing SQLite objects
$SQLiteCommand.Dispose()
$SQLiteConnection.Close()
$SQLiteConnection.Dispose()

Полученный результат можно, например, отправлять на почту, а сам скрипт запускать по расписанию.

Полный скрипт

[CmdletBinding()]
Param(
    # Name of DHCP server
    [Parameter(Mandatory=$True)][string]$ComputerName,
    
    # Directory of SQLite database file
    [Parameter(Mandatory=$True)][string]$DBDirectory,
    
    # Include reserved addresses in query
    [switch]$IncludeReservations
)

BEGIN {
    # Adding SQLite libraries, should be two files in script directory: System.Data.SQLite.dll, SQLite.Interop.dll
    Add-Type -Path "$PSScriptRootSystem.Data.SQLite.dll" -ErrorAction Stop

    # Function to execute command non query
    function ExecuteNonQuery($commandtext) {
        $SQLiteCommand.CommandText = $commandtext
        [void]$SQLiteCommand.ExecuteNonQuery()
    }

    # Function to execute command query
    function ExecuteReader($commandtext) {
        $SQLiteCommand.CommandText = $commandtext
        $DataReader = $SQLiteCommand.ExecuteReader()
        $Data = New-Object -TypeName System.Data.DataTable
        $Data.Load($DataReader)
        $DataReader.Close()
        return $Data
    }

    # Converting value to string, returns empty string from null value
    function str($value) {
        if ($value -ne $null) { return $value.ToString() }
        else { return [string]::Empty }
    }

    # Getting DHCP scopes and leases from DHCP server, excludes reserved addresses if -IncludeReservations not specified as parameter
    Write-Verbose "Getting leases from $ComputerName"
    $Scopes = Get-DhcpServerv4Scope -ComputerName $ComputerName
    if ($IncludeReservations) {
        $Leases = foreach ($Scope in $Scopes) { Get-DhcpServerv4Lease -ScopeId $Scope.ScopeId -ComputerName $ComputerName }  
    }
    else {
        $Leases = foreach ($Scope in $Scopes) { Get-DhcpServerv4Lease -ScopeId $Scope.ScopeId -ComputerName $ComputerName | where AddressState -notlike "*Reservation" }
    }
    
    # Creating and opening SQLite DB connection
    # If DB file does not exist, create a new file. DB file name is "<name_of_server>.sqlite"
    $SQLiteConnection = New-Object -TypeName System.Data.SQLite.SQLiteConnection
    $DBFile = "$DBDirectory$ComputerName.sqlite"
    if (Test-Path $DBFile) {
        Write-Verbose "Opening $DBFile"
        $SQLiteConnection.ConnectionString = "Data Source=$DBFile;"
    }
    else {
        Write-Verbose "Creating $DBFile"            
        $SQLiteConnection.ConnectionString = "Data Source=$DBFile;New=True;"
    }
    $SQLiteConnection.Open()

    # Create SQLite command object using previously created DB connection
    $SQLiteCommand = New-Object -TypeName System.Data.SQLite.SQLiteCommand
    $SQLiteCommand.Connection = $SQLiteConnection
}

PROCESS {
    # Getting current date and choosing the name of table for current date
    $CurrentTableName = "_$((Get-Date -Format "yyyyMMdd").ToString())"

    # Check if table for current date exists. If not, create a new one
    if ($CurrentTableName -ne (ExecuteReader("SELECT name FROM sqlite_master WHERE type = 'table' AND name = '$CurrentTableName';") | select -ExpandProperty name)) {
        Write-Verbose "Creating table $CurrentTableName"
        ExecuteNonQuery("CREATE TABLE $CurrentTableName (IPAddress PRIMARY KEY, ScopeId, AddressState, ClientId, HostName);")
    }
    else { Write-Verbose "Table $CurrentTableName exists" }

    # Update the current date table with collected leases
    Write-Verbose "Updating table $CurrentTableName"
    foreach ($Lease in $Leases) {
        ExecuteNonQuery("INSERT OR IGNORE INTO $CurrentTableName VALUES('$(str($Lease.IPAddress))', '$(str($Lease.ScopeId))', '$(str($Lease.AddressState))', '$(str($Lease.ClientId))', '$(str($Lease.HostName))');")  
    }

    # Selecting last two tables (by date) from database to compare and check difference
    # Compare tables and print the difference between them. New leases from previous date to current date will be printed
    $TablesToCompare = ExecuteReader("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name DESC LIMIT 2;")
    if ($TablesToCompare.Count -gt 1) {
        Write-Verbose "Going to compare"
        $LastTableName = $TablesToCompare[0] | select -ExpandProperty name
        $PrevTableName = $TablesToCompare[1] | select -ExpandProperty name
        $Result = ExecuteReader("SELECT * FROM $LastTableName WHERE $LastTableName.ClientId NOT IN (SELECT ClientId FROM $PrevTableName);")
        write "`r`nNew leases between $($PrevTableName.Trim("_")) and $($LastTableName.Trim("_"))"
        $Result | ft -Wrap -AutoSize 
    }
    else {
        write "No new leases"
        Write-Verbose "Not enough data to compare. First run?"
    }
}

END {
    # Closing and disposing SQLite objects
    $SQLiteCommand.Dispose()
    $SQLiteConnection.Close()
    $SQLiteConnection.Dispose()
}

Пример запуска: patrol.ps1 -ComputerName mydhcp.domain -DBDirectory D:Temp

Автор: t1meless

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js