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 { ...
}