From 36a434adb786af0610d47850304314a2fc589068 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Tue, 14 Oct 2025 06:06:39 -0600 Subject: [PATCH] Update Workflows/Windows/Windows Server/Roles/DFS/Creating and Configuring DFS Namespaces with Replication.md --- ...iguring DFS Namespaces with Replication.md | 272 +++++++++++++++++- 1 file changed, 271 insertions(+), 1 deletion(-) diff --git a/Workflows/Windows/Windows Server/Roles/DFS/Creating and Configuring DFS Namespaces with Replication.md b/Workflows/Windows/Windows Server/Roles/DFS/Creating and Configuring DFS Namespaces with Replication.md index c9118b7..9a441e3 100644 --- a/Workflows/Windows/Windows Server/Roles/DFS/Creating and Configuring DFS Namespaces with Replication.md +++ b/Workflows/Windows/Windows Server/Roles/DFS/Creating and Configuring DFS Namespaces with Replication.md @@ -116,4 +116,274 @@ In the Replication wizard that appears after about a minute, you can configure t - Create replicated folder - Create membership objects - Update folder properties - - Create connections \ No newline at end of file + - Create connections + +### Checking DFS Status +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. + +```powershell +<# Show-DfsTopologyTable.ps1 (PowerShell 5.1 compatible) + Table: + Namespace | Member Folder Target(s) | Replication Locations | Namespace Servers + - One row per folder target (multi-line friendly). +#> + +[CmdletBinding()] +param( + [string]$DomainPrefix = "\\bunny-lab.io" # adjust if needed +) + +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 table (multi-line cells wrap) +#$rows | Format-Table -AutoSize -Wrap + +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 + [int[]]$MaxWidths = @(50, 60, 50, 28), + + [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 + $rows = @($Data | ForEach-Object { + # Build a string-only hashtable per row for consistent measurement + $o = @{} + foreach ($c in $Columns) { $o[$c] = [string]($_.$c) } + [pscustomobject]$o + }) + + $widths = @() + for ($i=0; $i -lt $Columns.Count; $i++) { + $col = $Columns[$i] + $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() { + $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) { + $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) + Write-Host (DrawHeaderSep) + + $first = $true + foreach ($r in $rows) { + if (-not $first -and ([string]$r.$($Columns[0])) ) { + # Namespace changed → draw a heavy-ish separator + Write-Host (DrawSep) + } + $first = $false + Write-Host (DrawRow $r) + } + + Write-Host (DrawBottom) +} + +Write-DfsGrid -Data $rows +``` \ No newline at end of file