Хочу рассказать об одном из способов мониторить новые аренды адресов на 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