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


function Get-DFSServers {
    <#
    .SYNOPSIS
        Возвращает уникальные имена серверов, шары которых используются
        как цели в DFS-пространствах домена. Без модуля DFSN.
    .PARAMETER DFSRootPath
        Корневой UNC-путь DFS, например \\mydep.corporate.ru\
    .EXAMPLE
        Get-DFSServers -DFSRootPath "\\mydep.corporate.ru\"
    #>
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$DFSRootPath
    )

    # ---------- нормализация пути ----------
    $DFSRootPath = $DFSRootPath.TrimEnd('\')
    if ($DFSRootPath -match '^\\\\') { $DFSRootPath = $DFSRootPath.Substring(2) }

    $domain      = ($DFSRootPath -split '\\')[0]
    $domainParts = $domain -split '\.'
    $dn          = ($domainParts | ForEach-Object { "DC=$_" }) -join ','
    $dfsConfigDN = "CN=Dfs-Configuration,CN=System,$dn"

    Write-Host ""
    Write-Host "=== Поиск DFS-целей ===" -ForegroundColor Cyan
    Write-Host ("[*] Корневой путь DFS : {0}" -f $DFSRootPath)
    Write-Host ("[*] Домен            : {0}" -f $domain)
    Write-Host ("[*] DN конфигурации  : {0}" -f $dfsConfigDN)
    Write-Host ""

    # ---------- подключение к AD ----------
    try {
        $root = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$dfsConfigDN")
        $searcher = New-Object System.DirectoryServices.DirectorySearcher($root)
        $searcher.Filter        = "(|(objectClass=msDFS-Namespacev2)(objectClass=msDFS-Linkv2))"
        $searcher.PageSize      = 1000
        $searcher.SearchScope   = [System.DirectoryServices.SearchScope]::Subtree
        [void]$searcher.PropertiesToLoad.Add("msDFS-TargetListv2")
        [void]$searcher.PropertiesToLoad.Add("name")
        [void]$searcher.PropertiesToLoad.Add("distinguishedName")
        [void]$searcher.PropertiesToLoad.Add("objectClass")
    }
    catch {
        Write-Host ("[!] Не удалось подключиться к AD по пути LDAP://{0} : {1}" -f $dfsConfigDN, $_) -ForegroundColor Red
        return
    }

    $uncRegex = [regex]'\\\\([^\\/<>"\s]+)\\[^\\/<>"\s]+'

    # key  : короткое имя сервера в нижнем регистре (нормализация регистра и FQDN)
    # value: PSObject { Name; Fqdn; Sources }
    $serversMap = @{}

    $objects = $searcher.FindAll()
    Write-Host ("[*] Найдено объектов в Dfs-Configuration: {0}" -f $objects.Count)
    Write-Host ""

    $objIndex = 0
    foreach ($result in $objects) {
        $objIndex++
        $objName = ($result.Properties['name']             | Select-Object -First 1)
        $objDN   = ($result.Properties['distinguishedname'] | Select-Object -First 1)
        $objCls  = ($result.Properties['objectclass']       | Where-Object { $_ -like 'msDFS*' } | Select-Object -First 1)
        if (-not $objCls) { $objCls = 'unknown' }

        $prop = $result.Properties["msdfs-targetlistv2"]
        if (-not $prop -or $prop.Count -eq 0) {
            Write-Host ("[{0,3}] {1,-16} '{2}' — нет целей, пропуск" -f $objIndex, $objCls, $objName) -ForegroundColor DarkGray
            continue
        }

        $raw = $prop[0]
        if ($null -eq $raw) {
            Write-Host ("[{0,3}] {1,-16} '{2}' — пустой атрибут, пропуск" -f $objIndex, $objCls, $objName) -ForegroundColor DarkGray
            continue
        }

        # ---------- декодирование значения атрибута ----------
        $candidates = New-Object System.Collections.Generic.List[string]
        if ($raw -is [byte[]]) {
            try { $candidates.Add([System.Text.Encoding]::Unicode.GetString($raw)) } catch {}
            try { $candidates.Add([System.Text.Encoding]::UTF8.GetString($raw))    } catch {}
            try { $candidates.Add([System.Text.Encoding]::ASCII.GetString($raw))   } catch {}
        }
        else {
            $candidates.Add([string]$raw)
        }

        $foundUncs = New-Object System.Collections.Generic.List[string]
        $usedEncoding = ''
        foreach ($cand in $candidates) {
            $clean = $cand -replace "`0", ''
            $clean = $clean.Trim([char]0xFEFF, ' ', "`r", "`n", "`t")
            if (-not $clean) { continue }

            $m = $uncRegex.Matches($clean)
            if ($m.Count -gt 0) {
                foreach ($match in $m) { [void]$foundUncs.Add($match.Value) }
                $usedEncoding = 'OK'
                break
            }
        }

        if ($foundUncs.Count -eq 0) {
            Write-Host ("[{0,3}] {1,-16} '{2}' — не удалось извлечь UNC-пути (DN: {3})" -f $objIndex, $objCls, $objName, $objDN) -ForegroundColor Yellow
            continue
        }

        Write-Host ("[{0,3}] {1,-16} '{2}'" -f $objIndex, $objCls, $objName) -ForegroundColor Cyan
        Write-Host ("        DN: {0}" -f $objDN) -ForegroundColor DarkGray

        foreach ($unc in $foundUncs) {
            if ($unc -notmatch '^\\\\([^\\]+)\\') { continue }
            $fullName  = $matches[1]
            $shortName = ($fullName -split '\.')[0]
            $key       = $shortName.ToLowerInvariant()

            if (-not $serversMap.ContainsKey($key)) {
                $serversMap[$key] = [pscustomobject]@{
                    Name    = $shortName
                    Fqdn    = $null
                    Sources = New-Object System.Collections.Generic.List[string]
                }
            }

            # Сохраняем FQDN, если он встретился
            if ($fullName -match '\.' -and -not $serversMap[$key].Fqdn) {
                $serversMap[$key].Fqdn = $fullName
            }

            $src = "{0} '{1}' -> {2}" -f $objCls, $objName, $unc
            if (-not $serversMap[$key].Sources.Contains($src)) {
                [void]$serversMap[$key].Sources.Add($src)
            }

            Write-Host ("        -> UNC: {0}" -f $unc) -ForegroundColor DarkGreen
            Write-Host ("           источник: {0} '{1}'" -f $objCls, $objName) -ForegroundColor DarkGray
        }
        Write-Host ""
    }

    # ---------- итог ----------
    Write-Host "============================================================" -ForegroundColor Green
    Write-Host ("Уникальных серверов: {0}" -f $serversMap.Count) -ForegroundColor Green
    Write-Host "============================================================" -ForegroundColor Green

    $serversMap.Values |
        Sort-Object { $_.Name } |
        ForEach-Object {
            $label = $_.Name
            if ($_.Fqdn) { $label = "{0}  (short: {1})" -f $_.Fqdn, $_.Name }
            Write-Host ("  * {0}" -f $label) -ForegroundColor Green
            foreach ($s in $_.Sources) {
                Write-Host ("        от: {0}" -f $s) -ForegroundColor DarkGray
            }
        }
    Write-Host ""

    # Возвращаем объекты (Name — короткое, Fqdn — если встретился, Sources — список источников)
    return $serversMap.Values | Sort-Object { $_.Name }
}