Загрузка данных


#requires -version 2.0

<#
.SYNOPSIS
    Проверка отключения LLMNR и NetBIOS.

.DESCRIPTION
    Скрипт проверяет:
      - LLMNR
      - NetBIOS over TCP/IP
      - TCP/139
      - UDP/137-138

    Формат summary:
        COMPUTER01 - PASS
        COMPUTER02 - PASS WITH WARNINGS
        COMPUTER03 - FAIL

    Для каждого компьютера создается подробный файл:
        COMPUTER01.txt

    После успешного сохранения результата на сетевую шару
    файл переименовывается в:
        COMPUTER01_PASS.txt
        COMPUTER01_FAIL.txt
        COMPUTER01_PASS_WITH_WARNINGS.txt

    Дополнительно создается пустой marker-файл:
        COMPUTER01.marker

    Marker создается ТОЛЬКО после успешного сохранения
    и переименования сетевого отчета.

    Если marker существует, компьютер вообще не обращается
    к сетевой папке и проверка не выполняется повторно.

    Если сетевой путь недоступен:
      - результат сохраняется локально;
      - .marker НЕ создается;
      - при следующем запуске будет новая попытка.
#>


# ============================================================
# CONFIGURATION
# ============================================================

# Общая директория для результатов
$ResultDirectory = "\\10.68.16.120\report\netbios_llmnr_check"

# Общий файл со сводкой
$SummaryFile = Join-Path $ResultDirectory "summary.txt"

# Имя компьютера
$ComputerName = $env:COMPUTERNAME

# ============================================================
# MARKER FILE
# ============================================================

# Пустой marker-файл.
#
# Например:
#
# PC-001.marker
#
# Сам факт наличия этого файла означает:
# "этот компьютер уже успешно сохранил результат на сервере"

$MarkerFile = Join-Path `
    $ResultDirectory `
    ($ComputerName + ".marker")


# ============================================================
# INITIALIZATION
# ============================================================

$ErrorActionPreference = "SilentlyContinue"


# ============================================================
# MARKER CHECK
# ============================================================

# Проверяем ТОЛЬКО marker-файл.
#
# Если он существует — компьютер уже обработан.
#
# ВАЖНО:
# Если marker существует, к другим сетевым файлам
# вообще не обращаемся.

if (Test-Path $MarkerFile) {

    Write-Host ""
    Write-Host "============================================================"
    Write-Host "NETBIOS + LLMNR POLICY AUDIT"
    Write-Host "============================================================"
    Write-Host ""
    Write-Host "Computer: $ComputerName"
    Write-Host ""
    Write-Host "Audit already completed."
    Write-Host ""
    Write-Host "Marker:"
    Write-Host $MarkerFile
    Write-Host ""
    Write-Host "No action required."
    Write-Host "============================================================"

    exit 0
}


# ============================================================
# INITIALIZATION
# ============================================================

$Results = New-Object System.Collections.ArrayList

$FailCount = 0
$WarnCount = 0
$PassCount = 0


# ============================================================
# FUNCTIONS
# ============================================================

function Add-Result {
    param(
        [string]$Status,
        [string]$Name,
        [string]$Details
    )

    $Line = "[{0}] {1}: {2}" -f $Status, $Name, $Details

    [void]$Results.Add($Line)

    switch ($Status) {

        "PASS" {
            $script:PassCount++
        }

        "FAIL" {
            $script:FailCount++
        }

        "WARN" {
            $script:WarnCount++
        }
    }
}


function Add-Section {
    param(
        [string]$Title
    )

    $Separator = ""
    $Separator = $Separator.PadLeft(60, "-")

    [void]$Results.Add("")
    [void]$Results.Add($Separator)
    [void]$Results.Add($Title)
    [void]$Results.Add($Separator)
}


function Get-RegistryValueSafe {
    param(
        [string]$Path,
        [string]$Name
    )

    try {

        $Property = Get-ItemProperty `
            -Path $Path `
            -Name $Name `
            -ErrorAction Stop

        return $Property.$Name

    }
    catch {

        return $null
    }
}


# ============================================================
# SYSTEM INFORMATION
# ============================================================

$OS = Get-WmiObject Win32_OperatingSystem

$ComputerSystem = Get-WmiObject Win32_ComputerSystem

$DateTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

$OSCaption = $OS.Caption
$OSVersion = $OS.Version
$OSBuild = $OS.BuildNumber

$Domain = $ComputerSystem.Domain

$CurrentUser = `
    $env:USERDOMAIN + "\" + $env:USERNAME


# ============================================================
# REPORT HEADER
# ============================================================

[void]$Results.Add(
    "============================================================"
)

[void]$Results.Add(
    "DOMAIN SECURITY AUDIT"
)

[void]$Results.Add(
    "============================================================"
)

[void]$Results.Add("")

[void]$Results.Add("Computer : $ComputerName")
[void]$Results.Add("Domain   : $Domain")
[void]$Results.Add("OS       : $OSCaption")
[void]$Results.Add("Version  : $OSVersion")
[void]$Results.Add("Build    : $OSBuild")
[void]$Results.Add("User     : $CurrentUser")
[void]$Results.Add("Date     : $DateTime")


# ============================================================
# 1. LLMNR
# ============================================================

Add-Section "1. LLMNR"

# просто проверка реестра

$LLMNRPath = `
    "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient"

$EnableMulticast = Get-RegistryValueSafe `
    -Path $LLMNRPath `
    -Name "EnableMulticast"


if ($null -eq $EnableMulticast) {

    Add-Result `
        -Status "FAIL" `
        -Name "LLMNR policy" `
        -Details "EnableMulticast отсутствует. LLMNR не считается отключенным."

}
elseif ([int]$EnableMulticast -eq 0) {

    Add-Result `
        -Status "PASS" `
        -Name "LLMNR policy" `
        -Details "EnableMulticast = 0. LLMNR отключен."

}
else {

    Add-Result `
        -Status "FAIL" `
        -Name "LLMNR policy" `
        -Details "EnableMulticast = $EnableMulticast. LLMNR включен."
}


# ============================================================
# 2. NETBIOS OVER TCP/IP
# ============================================================

Add-Section "2. NETBIOS over TCP/IP"

# получение списка всех сетевых адаптеров

$Adapters = Get-WmiObject `
    Win32_NetworkAdapterConfiguration `
    -Filter "IPEnabled=True"


if ($null -eq $Adapters) {

    Add-Result `
        -Status "WARN" `
        -Name "NetBIOS adapters" `
        -Details "IP-enabled сетевые адаптеры не найдены."

}
else {

    $NetBIOSFailure = $false

    foreach ($Adapter in $Adapters) {

        $Description = $Adapter.Description

        $Index = $Adapter.Index

        $TcpipNetbiosOptions = `
            $Adapter.TcpipNetbiosOptions

        # TcpipNetbiosOptions:
        #
        # 0 = NetBIOS configuration from DHCP
        # 1 = NetBIOS enabled
        # 2 = NetBIOS disabled


        if ($TcpipNetbiosOptions -eq 2) {

            Add-Result `
                -Status "PASS" `
                -Name "NetBIOS adapter $Index" `
                -Details "$Description -> TcpipNetbiosOptions = 2. NetBIOS disabled."

        }
        elseif ($TcpipNetbiosOptions -eq 1) {

            Add-Result `
                -Status "FAIL" `
                -Name "NetBIOS adapter $Index" `
                -Details "$Description -> TcpipNetbiosOptions = 1. NetBIOS ENABLED."

            $NetBIOSFailure = $true

        }
        elseif ($TcpipNetbiosOptions -eq 0) {

            Add-Result `
                -Status "FAIL" `
                -Name "NetBIOS adapter $Index" `
                -Details "$Description -> TcpipNetbiosOptions = 0. NetBIOS controlled by DHCP."

            $NetBIOSFailure = $true

        }
        else {

            Add-Result `
                -Status "WARN" `
                -Name "NetBIOS adapter $Index" `
                -Details "$Description -> неизвестное значение: $TcpipNetbiosOptions."

            $NetBIOSFailure = $true
        }
    }


    if ($NetBIOSFailure) {

        Add-Result `
            -Status "FAIL" `
            -Name "NetBIOS overall" `
            -Details "Как минимум один IP-enabled адаптер не имеет принудительно отключенного NetBIOS."

    }
    else {

        Add-Result `
            -Status "PASS" `
            -Name "NetBIOS overall" `
            -Details "На всех IP-enabled адаптерах NetBIOS over TCP/IP отключен."
    }
}


# ============================================================
# 3. TCP/139
# ============================================================

Add-Section "3. TCP/139"

$TCP139 = netstat -ano -p tcp 2>$null |
    Select-String "LISTENING" |
    Select-String ":139"


if ($TCP139) {

    [void]$Results.Add("TCP/139 listeners:")

    [void]$Results.Add(
        ($TCP139 | Out-String).Trim()
    )

    Add-Result `
        -Status "FAIL" `
        -Name "TCP/139" `
        -Details "Обнаружен LISTENING TCP/139."

}
else {

    Add-Result `
        -Status "PASS" `
        -Name "TCP/139" `
        -Details "LISTENING TCP/139 не обнаружен."
}


# ============================================================
# 4. UDP/137-138
# ============================================================

Add-Section "4. UDP/137-138"

$UDPNetBIOS = netstat -ano -p udp 2>$null |
    Select-String ":(137|138)\s"


if ($UDPNetBIOS) {

    [void]$Results.Add("UDP NetBIOS listeners:")

    [void]$Results.Add(
        ($UDPNetBIOS | Out-String).Trim()
    )

    Add-Result `
        -Status "FAIL" `
        -Name "UDP/137-138" `
        -Details "Обнаружен UDP listener на NetBIOS портах."

}
else {

    Add-Result `
        -Status "PASS" `
        -Name "UDP/137-138" `
        -Details "UDP/137-138 listeners не обнаружены."
}


# ============================================================
# FINAL RESULT
# ============================================================

Add-Section "FINAL RESULT"

[void]$Results.Add("PASS : $PassCount")
[void]$Results.Add("WARN : $WarnCount")
[void]$Results.Add("FAIL : $FailCount")
[void]$Results.Add("")


# Определяем общий статус компьютера

if ($FailCount -eq 0 -and $WarnCount -eq 0) {

    $OverallStatus = "PASS"

    [void]$Results.Add(
        "RESULT: PASS"
    )

    [void]$Results.Add(
        "LLMNR и NetBIOS выглядят полностью отключенными."
    )

}
elseif ($FailCount -eq 0) {

    $OverallStatus = "PASS WITH WARNINGS"

    [void]$Results.Add(
        "RESULT: PASS WITH WARNINGS"
    )

    [void]$Results.Add(
        "Критических нарушений не обнаружено, но присутствуют предупреждения."
    )

}
else {

    $OverallStatus = "FAIL"

    [void]$Results.Add(
        "RESULT: FAIL"
    )

    [void]$Results.Add(
        "Обнаружены настройки, требующие исправления."
    )
}


# ============================================================
# PREPARE DETAILED RESULT
# ============================================================

$Header = ""

$Header = $Header.PadLeft(70, "=")

$Output = New-Object System.Collections.ArrayList


[void]$Output.Add("")

[void]$Output.Add($Header)

[void]$Output.Add(
    "COMPUTER: $ComputerName"
)

[void]$Output.Add(
    "DATE: $DateTime"
)

[void]$Output.Add($Header)


foreach ($Line in $Results) {

    [void]$Output.Add($Line)
}


[void]$Output.Add($Header)

[void]$Output.Add(
    "END OF AUDIT: $ComputerName"
)

[void]$Output.Add($Header)

[void]$Output.Add("")


# ============================================================
# PREPARE COMPUTER RESULT FILE
# ============================================================

$ComputerResultFile = Join-Path `
    $ResultDirectory `
    ($ComputerName + ".txt")


# ============================================================
# WRITE DETAILED COMPUTER FILE
# ============================================================

$ResultSavedToNetwork = $false


try {

    # Сначала создаём обычный файл:
    #
    # PC-001.txt

    $Output | Out-File `
        -FilePath $ComputerResultFile `
        -Encoding UTF8 `
        -Force `
        -ErrorAction Stop

    $ResultSavedToNetwork = $true

}
catch {

    # Если сеть недоступна —
    # сохраняем локально.

    $LocalLogDirectory = `
        "C:\netbios_llmnr_check"


    if (-not (Test-Path $LocalLogDirectory)) {

        New-Item `
            -Path $LocalLogDirectory `
            -ItemType Directory `
            -Force `
            -ErrorAction SilentlyContinue | Out-Null
    }


    $LocalResult = Join-Path `
        $LocalLogDirectory `
        ($ComputerName + ".txt")


    $Output | Out-File `
        -FilePath $LocalResult `
        -Encoding UTF8 `
        -Force
}


# ============================================================
# RENAME NETWORK RESULT TO STATUS FILE
# ============================================================

if ($ResultSavedToNetwork) {

    # Заменяем пробелы на "_"

    $StatusForFileName = `
        $OverallStatus.Replace(" ", "_")


    # Например:
    #
    # PC-001.txt
    #
    # превращается в:
    #
    # PC-001_PASS.txt
    # PC-001_FAIL.txt
    # PC-001_PASS_WITH_WARNINGS.txt

    $FinalFileName = `
        $ComputerName + "_" +
        $StatusForFileName +
        ".txt"


    $FinalResultFile = Join-Path `
        $ResultDirectory `
        $FinalFileName


    try {

        Write-Host ""
        Write-Host "Renaming result file:"
        Write-Host $ComputerResultFile
        Write-Host "TO:"
        Write-Host $FinalResultFile


        Rename-Item `
            -Path $ComputerResultFile `
            -NewName $FinalFileName `
            -Force `
            -ErrorAction Stop


        # ====================================================
        # VERIFY RENAME
        # ====================================================

        if (Test-Path $FinalResultFile) {

            Write-Host ""
            Write-Host "Rename successful."
            Write-Host "Final result:"
            Write-Host $FinalResultFile

            $MarkerFileCreated = $false


            # =================================================
            # CREATE EMPTY MARKER
            # =================================================

            try {

                # Создаём пустой marker-файл.
                #
                # Например:
                #
                # PC-001.marker

                New-Item `
                    -Path $MarkerFile `
                    -ItemType File `
                    -Force `
                    -ErrorAction Stop | Out-Null


                # Проверяем, что marker действительно появился.

                if (Test-Path $MarkerFile) {

                    $MarkerFileCreated = $true

                    Write-Host ""
                    Write-Host "Marker created:"
                    Write-Host $MarkerFile
                }

            }
            catch {

                Write-Host ""
                Write-Host "WARNING: Failed to create marker file."
                Write-Host $_.Exception.Message
            }


            # =================================================
            # IMPORTANT
            # =================================================

            # Marker является признаком того,
            # что сетевой результат полностью сохранен.
            #
            # Если marker создать не удалось,
            # при следующем запуске проверка повторится.

            if ($MarkerFileCreated) {

                $MarkerFile = $MarkerFile

            }
            else {

                $MarkerFile = $null
            }

        }
        else {

            Write-Host ""
            Write-Host "ERROR: Rename command completed,"
            Write-Host "but final result file was not found."

            $MarkerFile = $null
        }

    }
    catch {

        Write-Host ""
        Write-Host "============================================================"
        Write-Host "ERROR: FAILED TO RENAME RESULT FILE"
        Write-Host "============================================================"
        Write-Host ""
        Write-Host "Computer:"
        Write-Host $ComputerName
        Write-Host ""
        Write-Host "Source:"
        Write-Host $ComputerResultFile
        Write-Host ""
        Write-Host "Destination:"
        Write-Host $FinalResultFile
        Write-Host ""
        Write-Host "Error:"
        Write-Host $_.Exception.Message
        Write-Host ""
        Write-Host "============================================================"

        $MarkerFile = $null
    }
}


# ============================================================
# WRITE SUMMARY
# ============================================================

try {

    # В общий файл пишем только:
    #
    # COMPUTER - STATUS

    $SummaryLine = "{0} - {1}" -f `
        $ComputerName, `
        $OverallStatus


    Add-Content `
        -Path $SummaryFile `
        -Value $SummaryLine `
        -Encoding UTF8 `
        -ErrorAction Stop

}
catch {

    # Summary недоступен.
    #
    # Подробный результат уже сохранен.
}


# ============================================================
# CONSOLE OUTPUT
# ============================================================

Write-Host ""

Write-Host "============================================================"

Write-Host "DOMAIN SECURITY AUDIT"

Write-Host "============================================================"

Write-Host ""


foreach ($Line in $Results) {

    Write-Host $Line
}


Write-Host ""

Write-Host "============================================================"

Write-Host "Computer result:"

Write-Host ""


if ($ResultSavedToNetwork) {

    if ($null -ne $FinalResultFile) {

        Write-Host $FinalResultFile
    }
    else {

        Write-Host $ComputerResultFile
    }

}
else {

    Write-Host "Network unavailable."

    Write-Host "Local result:"

    Write-Host `
        "C:\netbios_llmnr_check\$ComputerName.txt"
}


Write-Host ""

Write-Host "Marker:"

if (Test-Path $MarkerFile) {

    Write-Host $MarkerFile
}
else {

    Write-Host "NOT CREATED"
}


Write-Host ""

Write-Host "Overall status:"

Write-Host $OverallStatus

Write-Host "============================================================"