Files
docs/scripts/Applications/Files and Collaboration/DFS/Report DFS Namespaces and Replication.md
T
nicole 289769a601
Automatic Documentation Deployment / Sync Docs to https://kb.bunny-lab.io (push) Successful in 8s
Restructured Documentation
2026-09-05 14:08:43 -06:00

9.9 KiB

tags
tags
DFS
Active Directory
PowerShell

Purpose

Report the DFS namespaces, folder targets, and replication configuration in the current Active Directory domain. Run the script in PowerShell with the DFSN and DFSR modules and permission to query the domain.

Script

You may want to put together a simple table report of the DFS namespaces, replication info, and target folders. You can run the following powershell script to generate a nice table-based report of the current structure of the DFS namespaces in your domain.

??? example "Powershell Reporting Script"

```powershell
# Automatically detect current AD domain and use it as DFS prefix
try {
    $Domain = ([System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()).Name
    $DomainPrefix = "\\$Domain"
} catch {
    Write-Warning "Unable to detect domain automatically. Falling back to manual value."
    $DomainPrefix = "\\bunny-lab.io"
}

Import-Module DFSN -ErrorAction Stop
Import-Module DFSR -ErrorAction Stop

function Get-ServerNameFromPath {
  param([string]$Path)
  if ([string]::IsNullOrWhiteSpace($Path)) { return $null }
  if ($Path -like "\\*") { return ($Path -split '\\')[2] }
  return $null
}
function Get-Max3 {
  param([int[]]$Values)
  if (-not $Values) { return 0 }
  return (($Values | Measure-Object -Maximum).Maximum)
}

# Build: GroupName (lower) -> memberships[]
$allGroups = Get-DfsReplicationGroup -ErrorAction SilentlyContinue
$groupMembershipMap = @{}
foreach ($g in $allGroups) {
  $ms = Get-DfsrMembership -GroupName $g.GroupName -ErrorAction SilentlyContinue
  $groupMembershipMap[$g.GroupName.ToLower()] = $ms
}

# Flatten all memberships for regex fallback
$allMemberships = @()
foreach ($arr in $groupMembershipMap.Values) { if ($arr) { $allMemberships += $arr } }

$rows = New-Object System.Collections.Generic.List[psobject]

# Enumerate namespace roots
$roots = Get-DfsnRoot -ErrorAction Stop | Where-Object { $_.Path -like "$DomainPrefix\*" }

Write-Host "DFS Namespace and Replication Overview" -ForegroundColor Cyan
Write-Host "------------------------------------------------------`n"

foreach ($root in $roots) {

  $rootPath  = $root.Path
  $rootLeaf  = ($rootPath -split '\\')[-1]

  $nsServers = @()
  $rootTargets = Get-DfsnRootTarget -Path $rootPath -ErrorAction SilentlyContinue
  foreach ($rt in $rootTargets) {
    $srv = Get-ServerNameFromPath $rt.TargetPath
    if ($srv) { $nsServers += $srv }
  }

  # Folders under this root
  $folders = Get-DfsnFolder -Path "$rootPath\*" -ErrorAction SilentlyContinue | Sort-Object Path

  foreach ($f in $folders) {
    $namespaceFull = $f.Path
    $leaf          = ($f.Path -split '\\')[-1]

    # DFSN folder targets
    $targets = Get-DfsnFolderTarget -Path $f.Path -ErrorAction SilentlyContinue
    $targets = @($targets | Sort-Object { Get-ServerNameFromPath $_.TargetPath })  # ensure array

    # Map to DFSR group by naming; fallback to regex on ContentPath
    $candidateGroup = ((($rootPath -replace '^\\\\','') + '\' + $leaf).ToLower())
    if ($groupMembershipMap.ContainsKey($candidateGroup)) {
      $msForFolder = $groupMembershipMap[$candidateGroup]
    } else {
      $escapedRootLeaf = [regex]::Escape($rootLeaf)
      $escapedLeaf     = [regex]::Escape($leaf)
      $regex = "\\$escapedRootLeaf\\$escapedLeaf($|\\)"
      $msForFolder = $allMemberships | Where-Object { $_.ContentPath -imatch $regex }
    }
    $msForFolder = @($msForFolder) # normalize to array

    # Build aligned rows: one per target
    $targetLines = @()
    $replLines   = @()

    foreach ($t in $targets) {
      $tServer = Get-ServerNameFromPath $t.TargetPath
      $targetLines += $t.TargetPath

      $msForServer = $null
      if ($msForFolder.Count -gt 0) {
        $msForServer = $msForFolder | Where-Object { $_.ComputerName -ieq $tServer } | Select-Object -First 1
      }
      if ($msForServer -and $msForServer.ContentPath) { $replLines += $msForServer.ContentPath } else { $replLines += '' }
    }

    # Max line count for row expansion (PS 5.1 safe)
    $maxLines = Get-Max3 @($targetLines.Count, $replLines.Count, $nsServers.Count)

    for ($i = 0; $i -lt $maxLines; $i++) {

      # Precompute values (PS 5.1: no inline-if in hashtables)
      $nsVal = ''
      if ($i -eq 0) { $nsVal = $namespaceFull }

      $targetVal = ''
      if ($i -lt $targetLines.Count) { $targetVal = $targetLines[$i] }

      $replVal = ''
      if ($i -lt $replLines.Count) { $replVal = $replLines[$i] }

      $nsServerVal = ''
      if ($i -lt $nsServers.Count) { $nsServerVal = $nsServers[$i] }

      $row = [PSCustomObject]@{
        'Namespace'               = $nsVal
        'Member Folder Target(s)' = $targetVal
        'Replication Locations'   = $replVal
        'Namespace Servers'       = $nsServerVal
      }
      $rows.Add($row) | Out-Null
    }
  }
}

# Render as a PowerShell bordered grid with one-space left/right padding in every cell
function Write-DfsGrid {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [System.Collections.IEnumerable]$Data,

    [string[]]$Columns = @('Namespace','Member Folder Target(s)','Replication Locations','Namespace Servers'),

    # Reasonable max widths; tune to your console (these are content+padding widths)
    [int[]]$MaxWidths = @(70, 70, 52, 30),

    [switch]$Ascii  # use +-| instead of box-drawing if your console garbles Unicode
  )

  # Ensure arrays align
  if ($MaxWidths.Count -lt $Columns.Count) {
    $pad = New-Object System.Collections.Generic.List[int]
    $pad.AddRange($MaxWidths)
    for ($i=$MaxWidths.Count; $i -lt $Columns.Count; $i++) { $pad.Add(40) }
    $MaxWidths = $pad.ToArray()
  }

  # Characters
  if ($Ascii) {
    $H = @{ tl='+'; tr='+'; bl='+'; br='+'; hz='-'; vt='|'; tj='+'; mj='+'; bj='+' }
  } else {
    # Box-drawing
    $H = @{ tl='┌'; tr='┐'; bl='└'; br='┘'; hz='─'; vt='│'; tj='┬'; mj='┼'; bj='┴' }
    try { [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 } catch {}
  }

  function TruncPad([string]$s, [int]$w) {
    if ($null -eq $s) { $s = '' }
    $s = $s -replace '\r','' -replace '\t',' '
    if ($s.Length -le $w) { return $s.PadRight($w, ' ') }
    if ($w -le 1) { return $s.Substring(0, $w) }
    return ($s.Substring(0, $w-1) + '…')
  }

  # Materialize and compute widths (include one-space left/right padding for header and data)
  $rows = @($Data | ForEach-Object {
    $o = @{}
    foreach ($c in $Columns) { $o[$c] = [string]($_.$c) }
    [pscustomobject]$o
  })

  $widths = @()
  for ($i=0; $i -lt $Columns.Count; $i++) {
    $col = $Columns[$i]
    # Start with header length including padding
    $max = (" " + $col + " ").Length
    foreach ($r in $rows) {
      $len = (" " + [string]$r.$col + " ").Length
      if ($len -gt $max) { $max = $len }
    }
    $widths += [Math]::Min($max, $MaxWidths[$i])
  }

  # Line builders
  function DrawTop() {
      $line = $H.tl
      for ($i = 0; $i -lt $widths.Count; $i++) {
          $line += ($H.hz * $widths[$i])
          if ($i -lt ($widths.Count - 1)) {
              $line += $H.tj
          } else {
              $line += $H.tr
          }
      }
      $line
  }
  function DrawMid([string[]]$Columns, [int[]]$widths, $H) {
    $line = $H.vt
    for ($i=0; $i -lt $widths.Count; $i++) {
      $line += TruncPad (" " + $Columns[$i] + " ") $widths[$i]
      $line += $H.vt
    }
    $line
  }
  function DrawSep() {
    $line = $H.vt
    for ($i=0; $i -lt $widths.Count; $i++) {
      $line += ($H.hz * $widths[$i])
      $line += $H.vt
    }
    $line
  }
  function DrawHeaderSep() {
    $line = $H.vt
    for ($i=0; $i -lt $widths.Count; $i++) {
      $line += ($H.hz * $widths[$i])
      $line += $H.vt
    }
    $line
  }
  function DrawBottom() {
      $line = $H.bl
      for ($i = 0; $i -lt $widths.Count; $i++) {
          $line += ($H.hz * $widths[$i])
          if ($i -lt ($widths.Count - 1)) {
              $line += $H.bj
          } else {
              $line += $H.br
          }
      }
      $line
  }
  function DrawRow($r, [string[]]$Columns, [int[]]$widths, $H) {
    $line = $H.vt
    for ($i=0; $i -lt $widths.Count; $i++) {
      $val = [string]$r.($Columns[$i])
      $line += TruncPad (" " + $val + " ") $widths[$i]
      $line += $H.vt
    }
    $line
  }

  # Render with group separators between namespaces (when the Namespace cell is non-empty)
  Write-Host (DrawTop)
  Write-Host (DrawMid -Columns $Columns -widths $widths -H $H)
  Write-Host (DrawHeaderSep)

  $first = $true
  foreach ($r in $rows) {
    if (-not $first -and ([string]$r.$($Columns[0])) ) {
      # Namespace changed → draw a separator
      Write-Host (DrawSep)
    }
    $first = $false
    Write-Host (DrawRow -r $r -Columns $Columns -widths $widths -H $H)
  }

  Write-Host (DrawBottom)
}

Write-DfsGrid -Data $rows
```