Анализ поля RelayAgentInformation в логе DHCP сервиса. Часть 2


Update: скрипт опубликован в Script Senter Decode DHCP 82 Option (RelayAgentInformation)

В продолжении темы Анализ поля RelayAgentInformation в логе DHCP сервиса. В той статье была сформулирована задача и приведена заготовка для её решения путём расшифровки поля DHCP Option 82, которое содержится в журналах Windows DHCP серверов в столбце RelayAgentInformation.

В этой статье публикую полный скрипт, который считывает журналы со всех DHCP серверов зарегистрированных в Active Directory и помещает в файл DHCPHistory.txt полный отчёт содержащий сведения о точке подключения клиента: коммутатор, слот, порт, VLAN.

С учётом того, что в большой сети может быть много DHCP серверов и много клиентов, произведена серьёзная оптимизация скрипта для быстрой обработки журналов большого размера.

Часть информации по оптимизации этого скрипта опубликована в статье Powershell – превратности производительности. После этого были выполнены дополнительные действия по оптимизации скрипта, а также его облагораживание с помощью Powershell Script Analyzer (Анализатор скриптов Powershell).

Что было оптимизировано во второй фазе. Сначала конструкция:

Set-Variable -name AgentRemoteID -Value $(($AgentRemoteID -split "(..)" |
             Where-Object {$_} | Foreach-Object -Process { [CHAR]([CONVERT]::toint16($_,16)) } ) -join "") -Scope 1

была заменена на:

Set-Variable -name AgentRemoteID -Value $( ([regex]::Matches($AgentRemoteID,'..') | % {$_.Value}  | Foreach-Object -Process { [CHAR]([CONVERT]::toint16($_,16)) } ) -join "") -Scope 1

В моём частном тесте это дало выигрыш ещё на 3 секунды (!!!) в функции расшифровки RelayAgentInformation: время выполнения с 7+ секунд упало до 4+ секунд. Это стоимость конструкции Where-Object {$_} .

После было сделано ещё одно косметическое изменение:

Set-Variable -name AgentRemoteID -Value $( ([regex]::Matches($AgentRemoteID,'..').Value | Foreach-Object -Process { [CHAR]([CONVERT]::toint16($_,16)) } ) -join "") -Scope 1

Сокращение pipe на % {$_.Value} дало мизерную экономию в доли секунды, но на больших файлах это может вылиться в ощутимую экономию.

Конечный итог впечатляет: первый вариант скрипта отрабатывал около 30 секунд, финальный – около 4 секунд. Это фактически семикратное ускорение!

Собственно сам скрипт:

#
#
#
function GetDHCPLogPaths {

   Write-Verbose -Message ">>>> GetDHCPLogPaths: Start Processing "
   $dhcpservers = Get-DhcpServerInDC
   $dhcpservers | Foreach-Object -Process { 
			$s1 = Get-DhcpServerAuditLog -ComputerName $_.DnsName
			if ($s1.Enable) {
				"\\"+ $_.DnsName + "\" + ($s1.Path -replace ":","$") + "\DhcpSrvLog-*.log"
			}
		    }


} # End GetDHCPLogPaths

#
#
#
function DecodeDHCP82Option {
[CmdletBinding()]
    Param(
            [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
            [string]$RelayAgentInformation 
    )

####################
        function  AgentCircuitID {

        Write-Debug -Message ">>>>AgentCircuitID"

        # Suboption Num=01 Len=0x06=6
        # 
        $AgentCircuitIDlen = [CONVERT]::toint16($RelayAgentInformation.Substring(2,2),16) *2
        $AgentCircuitID = $RelayAgentInformation.Substring( 4,  $AgentCircuitIDlen )

        # SubOption Type=0 Len=0x04=4
        # 
        $AgentCircuitIDlen00 = [CONVERT]::toint16($AgentCircuitID.Substring(2,2),16) *2
        $AgentCircuitID00 = $AgentCircuitID.Substring( 4,  $AgentCircuitIDlen00 )

        Set-Variable -Name AgentCircuitIDvlan -Value $([CONVERT]::toint16($AgentCircuitID00.Substring(0,4),16)) -Scope 1
        Set-Variable -Name AgentCircuitIDslot -Value $([CONVERT]::toint16($AgentCircuitID00.Substring(4,2),16)) -Scope 1
        Set-Variable -Name AgentCircuitIDport -Value $([CONVERT]::toint16($AgentCircuitID00.Substring(6,2),16)) -Scope 1

        Set-Variable -Name RelayAgentInformation -Value $RelayAgentInformation.Substring(4 + $AgentCircuitIDlen) -Scope 1

        }


        function  AgentRemoteID {

        Write-Debug -Message ">>>>AgentRemoteID"

        # Suboption Num=02 Len=0x14=20
        # 
        $AgentRemoteIDlen = [CONVERT]::toint16($RelayAgentInformation.Substring(2,2),16) *2
        $AgentRemoteID = $RelayAgentInformation.Substring( 4,  $AgentRemoteIDlen )

        # SubOption Type=01 Len=0x12=18
        # 
        $AgentRemoteIDlen01 = [CONVERT]::toint16($AgentRemoteID.Substring(2,2),16) *2
        $AgentRemoteID = $AgentRemoteID.Substring( 4,  $AgentRemoteIDlen01 )

        Set-Variable -name AgentRemoteID -Value $( ([regex]::Matches($AgentRemoteID,'..').Value | Foreach-Object -Process { [CHAR]([CONVERT]::toint16($_,16)) } ) -join "") -Scope 1

        Set-Variable -Name RelayAgentInformation -Value $RelayAgentInformation.Substring(4 + $AgentRemoteIDlen) -Scope 1

        }


        function  SubscriberID {

        Write-Debug -Message ">>>>SubscriberID"

        }

        function  ServerIdentifier {

        Write-Debug -Message ">>>>SubscriberID"


        }

####################

# Main for DecodeDHCP82Option

    # DHCP Suboption 1
    $AgentCircuitIDvlan = $null
    $AgentCircuitIDslot = $null
    $AgentCircuitIDport = $null

    # DHCP Suboption 2
    $AgentRemoteID = $null

    # DHCP Suboption 6
    $SubscriberID = $null

    # DHCP Suboption 11
    $ServerIdentifier = $null

    # Remove "0x"
    $RelayAgentInformation=$RelayAgentInformation.Substring(2)

    # Decode DHCP Suboptions cycle
    while ($RelayAgentInformation) {

        Write-Debug -Message ">>>> Start RelayAgentInformation=$($RelayAgentInformation) "

	        switch ($RelayAgentInformation.Substring(0,2)) 
	            { 
	                "01" { AgentCircuitID } 
	                "02" { AgentRemoteID } 
	                "06" { SubscriberID } 
	                "11" { ServerIdentifier } 
	                default { Write-Verbose -Message "Unknown DHCP Suboption: $($RelayAgentInformation.Substring(0,2))" }
            }
        Write-Debug -Message ">>>> Stop RelayAgentInformation=$($RelayAgentInformation) "

    } # While


    return [psobject]@{
        AgentCircuitIDvlan = $AgentCircuitIDvlan
        AgentCircuitIDslot = $AgentCircuitIDslot
        AgentCircuitIDport = $AgentCircuitIDport
        AgentRemoteID = $AgentRemoteID
    }

} # End DecodeDHCP82Option


#
#
#
function ProcessDHCPLog {
[CmdletBinding()]
    Param(
            [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
            [string[]]$logfileName = ""
    )
BEGIN {
    # Header fields from DHCP log (check!)
    $colNames = "ID,Date,Time,Description,IP Address,Host Name,MAC Address,User Name,TransactionID,QResult,Probationtime,CorrelationID,Dhcid,VendorClass(Hex),VendorClass(ASCII),UserClass(Hex),UserClass(ASCII),RelayAgentInformation"

    # Add new output fields
    $colNames += ",oDateTime,sortDateTime,AgentCircuitIDvlan,AgentCircuitIDslot,AgentCircuitIDport,AgentRemoteID"

    $Header = $colNames -split ','
}
PROCESS {

    Write-Verbose -Message ">>>> ProcessDHCPLog: Start Processing: File=$($logfileName) "
            # Filter only "Renew" rows and "Assign" rows
    $content = Select-String -Path $logfileName -Pattern "^(10|11)," | Select-Object -ExpandProperty Line |
            # Convert frim CSV to PSObject
	        ConvertFrom-Csv -Header $Header | 
            # Filter only "Renew" rows and "Assign" rows
            Foreach-Object -Process { $_.sortDateTime = ([datetime]"$($_.Date) $($_.Time)").ToString("yyyyMMddHHmmss")
                if ( $_.sortDateTime -gt $script:UnuqueRows[$_.'MAC Address'].sortDateTime ) {
                        
                        # Decode DHCP 82 Option
                        if ( $_.RelayAgentInformation) { # exist DHCP Suboptions

                            $res82 = DecodeDHCP82Option -RelayAgentInformation $_.RelayAgentInformation

                            $_.AgentCircuitIDvlan = $res82.AgentCircuitIDvlan
                            $_.AgentCircuitIDslot = $res82.AgentCircuitIDslot
                            $_.AgentCircuitIDport = $res82.AgentCircuitIDport
                            $_.AgentRemoteID = $res82.AgentRemoteID
                        }

                        $script:UnuqueRows[$_.'MAC Address'] = $_  
                    } 
                        "1" # for rows count
                }
    Write-Verbose -Message "<<<< ProcessDHCPLog: Stop Processing: Count=$($content.count) File=$($logfileName) "

} # PROCESS

} # End ProcessDHCPLog

#
#
#
function ImportDHCPHistory {
[CmdletBinding()]
    Param(
            [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
            [string[]]$logfileName = ""
    )

    $content = Get-Content $logfileName -ErrorAction SilentlyContinue | 
            # Convert frim CSV to PSObject
	        ConvertFrom-Csv 
    $content | Foreach-Object -Process { $script:UnuqueRows[$_.'MAC Address'] = $_ }

    Write-Verbose -Message "<<<< ImportDHCPHistory: Count=$($content.count) File=$($logfileName) "

} # End ImportDHCPHistory


#
#
#
function SaveDHCPHistory {
[CmdletBinding()]
    Param(
            [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
            [string]$logfileName
    )

    if ( Test-Path -Path $logfileName ) {
        $file = Get-Item -Path $logfileName
        Rename-Item -Path $logfileName -NewName $($file.Name -replace ".txt$","$1-$(Get-Date -Format "yyyyMMddHHmmss").old")
    }

    $script:UnuqueRows.GetEnumerator() | Select-Object -ExpandProperty Value | export-csv -Path $logfileName -Encoding Unicode -NoTypeInformation

    Write-Verbose -Message "<<<< SaveDHCPHistory: Count=$($script:UnuqueRows) File=$($logfileName) "

} # End SaveDHCPHistory



#
# MAIN
#

$workpath = "C:\Install\DHCP82\"

$DHCPHistoryFile = $workpath + "DHCPHistory.txt"

# Unique DHCP-log rows (by MAC)
$Script:UnuqueRows = @{}
ImportDHCPHistory -logfileName $DHCPHistoryFile

# Get all dhcp log files
$logs = GetDHCPLogPaths | dir

# Process all dhcp log files
$logs | Select-Object -ExpandProperty FullName | ProcessDHCPLog

# Save DHCP report
SaveDHCPHistory -logfileName $DHCPHistoryFile

# End MAIN
Реклама

комментария 3

  1. Исходный код портят конструкции типа <pre>">>>&gt выложите пожалуйста очищенный код, если wordpress портит, то может в виде аттача. Или ссылки на googel docs,skydrive, dropbox и тд

  2. Поддерживаю предыдущего комментатора. Если не трудно, не могли бы Вы положить чистый скрипт в архив?

  3. Через пару дней сделаю архив и прицеплю к статье.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: