Public/healthcheck/Get-DfsNamespaceHealth.ps1

#Requires -Version 5.1
function Get-DfsNamespaceHealth {
    <#
        .SYNOPSIS
            Retrieves DFS Namespace health status from local or remote computers
 
        .DESCRIPTION
            Checks the DFS service state and enumerates all DFS namespace roots on
            the target computer. For each root, it queries root targets to determine
            how many are online versus offline, then computes an overall health status.
            Uses Invoke-Command for remote execution because the DFSN cmdlets only
            work locally on the DFS server.
 
        .PARAMETER ComputerName
            One or more computer names to query. Defaults to the local computer.
            Accepts pipeline input by value and by property name.
 
        .PARAMETER Credential
            Optional PSCredential for authenticating to remote computers.
            Not used for local queries.
 
        .EXAMPLE
            Get-DfsNamespaceHealth
 
            Returns DFS namespace health for all roots on the local computer.
 
        .EXAMPLE
            Get-DfsNamespaceHealth -ComputerName 'SRV01'
 
            Returns DFS namespace health from a single remote DFS server.
 
        .EXAMPLE
            'SRV01', 'SRV02' | Get-DfsNamespaceHealth
 
            Checks DFS namespace health on multiple servers via pipeline.
 
        .OUTPUTS
            PSWinOps.DfsNamespaceHealth
            Returns objects with computer name, DFS service status, root path,
            root type, state, target counts, and an overall health indicator.
 
        .NOTES
            Author: Franck SALLET
            Version: 1.0.0
            Last Modified: 2026-03-26
            Requires: PowerShell 5.1+ / Windows only
            Requires: DFSN module for full health analysis
 
        .LINK
            https://github.com/k9fr4n/PSWinOps
 
        .LINK
            https://learn.microsoft.com/en-us/powershell/module/dfsn/
    #>

    [CmdletBinding()]
    [OutputType('PSWinOps.DfsNamespaceHealth')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('CN', 'Name', 'DNSHostName')]
        [string[]]$ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $false)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting"
        $localNames = @($env:COMPUTERNAME, 'localhost', '.')

        $scriptBlock = {
            $timestamp = Get-Date -Format 'o'
            $resultList = [System.Collections.Generic.List[object]]::new()

            # Step 1 - Check DFS service
            try {
                $dfsSvc = Get-Service -Name 'Dfs' -ErrorAction Stop
                $svcStatus = $dfsSvc.Status.ToString()
            }
            catch {
                $svcStatus = 'NotFound'
            }

            # Step 2 - Check DFSN module availability
            $dfsnAvailable = $false
            if ($svcStatus -eq 'Running') {
                $dfsnAvailable = [bool](Get-Module -ListAvailable -Name 'DFSN' -ErrorAction SilentlyContinue)
            }

            if (-not $dfsnAvailable) {
                $health = if ($svcStatus -eq 'Running') {
                    'RoleUnavailable'
                }
                elseif ($svcStatus -eq 'NotFound') {
                    'RoleUnavailable'
                }
                else {
                    'Critical'
                }

                $resultList.Add([PSCustomObject]@{
                    PSTypeName     = 'PSWinOps.DfsNamespaceHealth'
                    ComputerName   = $env:COMPUTERNAME
                    ServiceName    = 'Dfs'
                    ServiceStatus  = $svcStatus
                    RootPath       = 'N/A'
                    RootType       = 'N/A'
                    State          = 'N/A'
                    TargetCount    = 0
                    HealthyTargets = 0
                    OverallHealth  = $health
                    Timestamp      = $timestamp
                })
                return $resultList.ToArray()
            }

            # Step 3 - Enumerate DFS roots
            $dfsRoots = $null
            try {
                $dfsRoots = @(Get-DfsnRoot -ErrorAction Stop)
            }
            catch {
                $resultList.Add([PSCustomObject]@{
                    PSTypeName     = 'PSWinOps.DfsNamespaceHealth'
                    ComputerName   = $env:COMPUTERNAME
                    ServiceName    = 'Dfs'
                    ServiceStatus  = $svcStatus
                    RootPath       = 'N/A'
                    RootType       = 'N/A'
                    State          = 'N/A'
                    TargetCount    = 0
                    HealthyTargets = 0
                    OverallHealth  = 'Critical'
                    Timestamp      = $timestamp
                })
                return $resultList.ToArray()
            }

            if ($dfsRoots.Count -eq 0) {
                $resultList.Add([PSCustomObject]@{
                    PSTypeName     = 'PSWinOps.DfsNamespaceHealth'
                    ComputerName   = $env:COMPUTERNAME
                    ServiceName    = 'Dfs'
                    ServiceStatus  = $svcStatus
                    RootPath       = 'N/A'
                    RootType       = 'N/A'
                    State          = 'N/A'
                    TargetCount    = 0
                    HealthyTargets = 0
                    OverallHealth  = 'Healthy'
                    Timestamp      = $timestamp
                })
                return $resultList.ToArray()
            }

            # Step 4 - Check each root and its targets
            foreach ($root in $dfsRoots) {
                $rootState = if ($root.State) { $root.State.ToString() } else { 'N/A' }
                $rootType  = if ($root.Type)  { $root.Type.ToString()  } else { 'N/A' }

                $targetTotal   = 0
                $targetHealthy = 0
                try {
                    $targets = @(Get-DfsnRootTarget -Path $root.Path -ErrorAction Stop)
                    $targetTotal = $targets.Count
                    foreach ($target in $targets) {
                        if ($target.State -eq 'Online') {
                            $targetHealthy++
                        }
                    }
                }
                catch {
                    $targetTotal   = 0
                    $targetHealthy = 0
                }

                $rootHealth = if ($targetTotal -eq 0) {
                    'Critical'
                }
                elseif ($targetHealthy -eq 0) {
                    'Critical'
                }
                elseif ($targetHealthy -lt $targetTotal) {
                    'Degraded'
                }
                else {
                    'Healthy'
                }

                $resultList.Add([PSCustomObject]@{
                    PSTypeName     = 'PSWinOps.DfsNamespaceHealth'
                    ComputerName   = $env:COMPUTERNAME
                    ServiceName    = 'Dfs'
                    ServiceStatus  = $svcStatus
                    RootPath       = $root.Path
                    RootType       = $rootType
                    State          = $rootState
                    TargetCount    = $targetTotal
                    HealthyTargets = $targetHealthy
                    OverallHealth  = $rootHealth
                    Timestamp      = $timestamp
                })
            }

            return $resultList.ToArray()
        }
    }

    process {
        foreach ($machine in $ComputerName) {
            try {
                $isLocal = $localNames -contains $machine
                $displayName = $machine.ToUpper()

                if ($isLocal) {
                    Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying local computer '${machine}'"
                    $rawResults = & $scriptBlock
                }
                else {
                    Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying remote computer '${machine}'"
                    $invokeParams = @{
                        ComputerName = $machine
                        ScriptBlock  = $scriptBlock
                        ErrorAction  = 'Stop'
                    }
                    if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
                        $invokeParams['Credential'] = $Credential
                    }
                    $rawResults = Invoke-Command @invokeParams
                }

                foreach ($item in $rawResults) {
                    $item.PSObject.TypeNames.Insert(0, 'PSWinOps.DfsNamespaceHealth')
                    $item.ComputerName = $displayName
                    $item
                }
            }
            catch {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${machine}': $_"
                continue
            }
        }
    }

    end {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed"
    }
}