DFS (Distributed File System) Mountpoints Across Sites
DFS (Distributed File System) is a very use feature. By normalizing the naming and presenting it via namespace servers, hosts in multiple sites connect to their local filers using abstracted names that are common across the environment. This is great until …
For some reason, we randomly have hosts that try to mount filers across sites. Normally, this is ‘only’ a performance hit. I say ‘only’ because it does affect the results if it’s say, a performance test pass. At other times, there are files stored to folders under those common names, but the folders themselves aren’t replicated. Our raw data is one case – one test pool’s results aren’t replicated to other labs, but if a machine is cross-mounting the RawData folder, then the data it logs will be lost.
DfsUtil.exe to the rescue. This is part of the Windows 2K3 SP1 Support Tools, and a feature on 2K8. While best practices state that you should use the version released for your OS, I’ve found that the 2K3 version works on 2K8 in the one key feature: dfsutil.exe /pktinfo. This displays a screenfull-plus of data for each mountpoint, including the current namespace server, alternate namespace servers, and far more esoterica than I know what to do with.
For each computer, I’m interested in three main facts:
- What is the mountpoint? (And, in the header comment block, you’ll see I’m interested only in a specific set of mountpoints.)
- What is the namespace server for that mountpoint?
- Is the namespace server for that mountpoint in the same lab?
(As is the case before, thanks to Keith Munson and Corey Lewis for laying the groundwork on this.)
function Get-DfsEntry {
<#
.Synopsis
Validate DFS mount points using dfsutil.exe
.Description
For each computer specified by -ComputerName, copy dfsutil.exe from value specified by -DfsUtilPath relative
to local machine to the location specified by -LocalDfsUtilPath, then execute it with the /pktinfo flag.
Parse the output of dfsutil.exe /pktinfo and tokenize into records, with start-of-record defined by a line
matching '^\s*Entry:'. A record is comprised of:
- The computer name.
- The mountpoint (the value following 'Entry:'.)
- The namespace server (the value preceding 'ACTIVE' or, failing that, the value preceding the first instance
of 'TARGETSET'.)
- The status. This dynamically generated and is dependent on the namespace server having the same first three
characters as the computer name (i.e. in the same site.)
NOTE: The passing criteria is case-specific. In our labs, we name hosts with the same first three characters
as the lab name.
Records are never returned for mountpoints that do not begin with the '\$env:UserDomain' (e.g. mountpoints that
do not begin with '\labPerf' or '\labE2E' because they are not DFS-managed mountpoints in our environment), that
are cross-site (e.g. \labPerf.local\olm\sites\B07), or are filetrees. Filetrees are defined with only one
namespace server per mountpoint, so they are considered canonical by definition.
By default, passing records are not returned. Only failing records are returned. Using the -Verbose switch
will return all records, pass or fail.
No matter the presence or absence of the -Verbose switch, all failing records are summarized and displayed via
Write-Warning upon processing all specified computers.
.Parameter ComputerName
List of remote computers on which to run dfsutil.exe /pktinfo
.Parameter DfsUtilPath
Location of dfsutil.exe on the local computer. Defaults to "C:\Program Files\Support Tools\dfsutil.exe".
.Parameter LocalDfsUtilPath
Location of dfsutil.exe on the remote computer. Defaults to "D:\temp\dfsutil.exe".
.Parameter Fix
If errors are detected, run "dfsutil.exe /pktflush" and "dfsutil.exe /spcflush" to clear cache.
NOTE: This does not guarantee it will rebind to the correct namespace server. After five minutes, please run
this test again to ensure it bound to the correct server.
.Parameter Verbose
Return all records, not just failing ones.
.Notes
When Who What Why
2013-05-25 timdunn 1.0 Release to operations
#>
param (
[Parameter(ValueFromPipeline=$true,Position=0)][string[]]$ComputerName,
[string]$DfsUtilPath = "C:\Program Files\Support Tools\dfsutil.exe",
[string]$LocalDfsUtilPath = "D:\temp\dfsutil.exe",
[switch]$Fix
);
begin {
# records of failed host/mounpoint/namespace server
$failed = @();
# the heavy lifting
$scriptBlock = {
param (
[string]$DfsUtilPath = $null,
[bool]$Fix = $false
);
function Out-Record {
# final processing and conditions under which a record is returned
param ([Object]$record);
# conditions by which a record is returned
if (
$record.Entry -and
($record.Entry -match "\\$env:UserDomain\W") -and
($record.Entry -notmatch "\\trees\\") -and
($record.Entry -notmatch "\\sites\\")
) {
if (!$record.Active) { $record.Active = $script:targetSet; }
$record.Status = if ($record.Active -match "^\\$script:site") {
"PASS";
} else {
if ($Fix) {
"FIXED";
$script:needToFix = $true;
} else {
"FAIL";
} # if ($Fix
} # if ($record.Active
Write-Debug "Object.Status = '$($record.Status)'";
Write-Debug "Outputting record";
$record | Select-Object -Property $script:Properties;
} # if ($record.Entry -and ...
} # function Out-Record
$script:needToFix = $false;
$script:targetSet = $null;
$script:Properties = @('ComputerName', 'Entry', 'Active', 'Status');
$script:site = $env:COMPUTERNAME.SubString(0,3)
if ($DfsUtilPath -and (Test-Path $DfsUtilPath)) {
Write-Progress (Get-Date) "Running $dfsUtilPath /pktinfo on $env:ComputerName";
& $DfsUtilPath /pktinfo | foreach -Begin {
$output = @();
} -Process {
$line = $_;
if ($line -match "^\s*Entry: (\S*)") {
Out-Record -Record $record;
# New record
$record = $true | Select-Object -Property $script:Properties;
if ($record.Entry = $matches[1]) { $record.Entry = $record.Entry.ToLower(); }
Write-Debug "Object.Entry = '$($record.Entry)'";
$record.ComputerName = $env:COMPUTERNAME;
Write-Debug "Object.ComputerName = '$($record.ComputerName)'";
} elseif ($line -match "^[^\[]*\[([^\]]*)\].*\sACTIVE\s") {
# Active mountpoint found
if ($record.Active = $matches[1]) { $record.Active = $record.Active.ToLower(); }
Write-Debug "Object.Active = '$($record.Active)'";
} elseif ($line -match "^[^\[]*\[([^\]]*)\].*\sTARGETSET\s") {
# Possible target mountpoint found.
if (!$script:targetSet -and ($script:targetSet = $matches[1])) { $script:targetSet = $script:targetSet.ToLower(); }
} else {
Write-Debug "No match: '$line'";
} # if ($line -match ...
} -End {
Out-Record -Record $record;
} # & $DfsUtilpath /pktinfo | foreach ...
if ($script:needToFix) {
Write-Progress (Get-Date) "Running $dfsUtilPath /pktflush on $env:ComputerName";
& $DfsUtilPath /pktflush | Out-Null;
Write-Progress (Get-Date) "Running $dfsUtilPath /spcflush on $env:ComputerName";
& $DfsUtilPath /spcflush | Out-Null;
} # if ($script:needToFix...
} else {
Write-Warning "-DfsUtilPath '$DfsUtilPath' on host '$env:ComputerName' not found";
} # if ($DfsUtilPath -and (Test-Path...
} # $scriptBlock = ...
} # begin { ...
process {
if (!(Test-Path $DfsUtilPath)) {
Write-Warning "-DfsUtilPath $DfsUtilPath not found";
return;
} # if (!(Test-Path $DfsUtilPath
foreach ($computer in $ComputerName) {
# ensure folder exists
$remoteDfsUtilDir = Split-Path -Parent -Path "\\$computer\$($LocalDfsUtilPath -replace ':','$')";
if (!(Test-Path $remoteDfsUtilDir)) { New-Item -ItemType Directory -Path $remoteDfsUtilDir -ErrorAction SilentlyContinue | Out-Null; }
$remoteDfsUtilPath = "$remoteDfsUtilDir\dfsutil.exe";
if (Test-Path $remoteDfsUtilDir) {
if (!(Test-Path $remoteDfsUtilPath)){
Write-Progress (Get-Date) "Copying $DfsUtilPath to $remoteDfsUtilPath";
Copy-Item $DfsUtilPath $remoteDfsUtilPath -Force -ErrorAction SilentlyContinue | Out-Null;
} # if (!(Test-Path $remoteDfsUtilPath ...
# if dfsutil.exe exists on remote machine
if (Test-Path $remoteDfsUtilPath) {
& {
# branch if target computer is localhost. For some reason "Invoke-Command -ComputerName $env:ComputerName" has problems.
if ($computer -eq $env:computerName) {
Invoke-Command $scriptBlock -ArgumentList $LocalDfsUtilPath, $Fix;
} else {
Invoke-Command $scriptBlock -ArgumentList $LocalDfsUtilPath, $Fix -ComputerName $computer;
} # if ($computer -eq ...
} | Select-Object -Property $script:Properties | % {
# buffer failing records for summary at the end, and display to STDOUT
if ($_.Status -eq 'FAIL') {
$failed += $_;
$_;
}
# display passing records if -verbose specified
if ($PSBoundParameters['Verbose']) { $_; }
} # ... | Select-Object -Property 'ComputerName' ...
} else {
Write-Warning "Unable to create or find file $remoteDfsUtilPath";
} # if (Test-Path $remoteDfsUtilPath ...
} else {
Write-Warning "Unable to create or find directory $remoteDfsUtilDir";
} # if (Test-Path $remoteDfsUtilDir ...
} # foreach ($computer in ...
} # process { ...
end {
# if any failing records are buffered, display as warning.
if ($failed.Count) { $failed | % { Write-Warning ("{0} {1} mounting {2}" -f $_.ComputerName, $_.Entry, $_.Active); } }
} # end { ...
}