Загрузка данных
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 }
}