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 39a2014..addda31 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 @@ -124,263 +124,266 @@ In the Replication wizard that appears after about a minute, you can configure t ### 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 -[CmdletBinding()] -param( - [string]$DomainPrefix = "\\bunny-lab.io" # Adjust if Different -) - -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 } +??? 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" } - $msForFolder = @($msForFolder) # normalize to array - # Build aligned rows: one per target - $targetLines = @() - $replLines = @() + Import-Module DFSN -ErrorAction Stop + Import-Module DFSR -ErrorAction Stop - foreach ($t in $targets) { - $tServer = Get-ServerNameFromPath $t.TargetPath - $targetLines += $t.TargetPath + 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) + } - $msForServer = $null - if ($msForFolder.Count -gt 0) { - $msForServer = $msForFolder | Where-Object { $_.ComputerName -ieq $tServer } | Select-Object -First 1 + # 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 } } - 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) + # Folders under this root + $folders = Get-DfsnFolder -Path "$rootPath\*" -ErrorAction SilentlyContinue | Sort-Object Path - for ($i = 0; $i -lt $maxLines; $i++) { + foreach ($f in $folders) { + $namespaceFull = $f.Path + $leaf = ($f.Path -split '\\')[-1] - # Precompute values (PS 5.1: no inline-if in hashtables) - $nsVal = '' - if ($i -eq 0) { $nsVal = $namespaceFull } + # DFSN folder targets + $targets = Get-DfsnFolderTarget -Path $f.Path -ErrorAction SilentlyContinue + $targets = @($targets | Sort-Object { Get-ServerNameFromPath $_.TargetPath }) # ensure array - $targetVal = '' - if ($i -lt $targetLines.Count) { $targetVal = $targetLines[$i] } + # 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 - $replVal = '' - if ($i -lt $replLines.Count) { $replVal = $replLines[$i] } + # Build aligned rows: one per target + $targetLines = @() + $replLines = @() - $nsServerVal = '' - if ($i -lt $nsServers.Count) { $nsServerVal = $nsServers[$i] } + foreach ($t in $targets) { + $tServer = Get-ServerNameFromPath $t.TargetPath + $targetLines += $t.TargetPath - $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 + $msForServer = $null + if ($msForFolder.Count -gt 0) { + $msForServer = $msForFolder | Where-Object { $_.ComputerName -ieq $tServer } | Select-Object -First 1 } - } - $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 + 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 + } } - $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) + # 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, - $first = $true - foreach ($r in $rows) { - if (-not $first -and ([string]$r.$($Columns[0])) ) { - # Namespace changed → draw a heavy-ish separator - Write-Host (DrawSep) + [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) } - $first = $false - Write-Host (DrawRow $r) - } - Write-Host (DrawBottom) -} - -Write-DfsGrid -Data $rows -``` \ No newline at end of file + Write-DfsGrid -Data $rows + ``` \ No newline at end of file