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: Module DFSN (FS-DFS-Namespace + RSAT-DFS-Mgmt-Con)
 
        .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
    )

    begin {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting"

        $scriptBlock = {
            $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) {
                $resultList.Add(@{
                    ServiceStatus  = $svcStatus
                    RootPath       = 'N/A'
                    RootType       = 'N/A'
                    State          = 'N/A'
                    TargetCount    = 0
                    HealthyTargets = 0
                    DfsnAvailable  = $false
                    QueryError     = $false
                })
                return $resultList.ToArray()
            }

            # Step 3 - Enumerate DFS roots
            $dfsRoots = $null
            try {
                $dfsRoots = @(Get-DfsnRoot -ErrorAction Stop)
            }
            catch {
                $resultList.Add(@{
                    ServiceStatus  = $svcStatus
                    RootPath       = 'N/A'
                    RootType       = 'N/A'
                    State          = 'N/A'
                    TargetCount    = 0
                    HealthyTargets = 0
                    DfsnAvailable  = $true
                    QueryError     = $true
                })
                return $resultList.ToArray()
            }

            if ($dfsRoots.Count -eq 0) {
                $resultList.Add(@{
                    ServiceStatus  = $svcStatus
                    RootPath       = 'N/A'
                    RootType       = 'N/A'
                    State          = 'N/A'
                    TargetCount    = 0
                    HealthyTargets = 0
                    DfsnAvailable  = $true
                    QueryError     = $false
                })
                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
                }

                $resultList.Add(@{
                    ServiceStatus  = $svcStatus
                    RootPath       = $root.Path
                    RootType       = $rootType
                    State          = $rootState
                    TargetCount    = $targetTotal
                    HealthyTargets = $targetHealthy
                    DfsnAvailable  = $true
                    QueryError     = $false
                })
            }

            return $resultList.ToArray()
        }
    }

    process {
        foreach ($machine in $ComputerName) {
            try {
                $displayName = $machine.ToUpper()
                Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying '${machine}'"
                $rawResults = Invoke-RemoteOrLocal -ComputerName $machine -ScriptBlock $scriptBlock -Credential $Credential

                foreach ($item in $rawResults) {
                    # Compute OverallHealth outside the scriptblock
                    $healthStatus = if ($item.ServiceStatus -eq 'NotFound') {
                        [PSWinOpsHealthStatus]::RoleUnavailable
                    }
                    elseif ($item.ServiceStatus -ne 'Running') {
                        [PSWinOpsHealthStatus]::Critical
                    }
                    elseif (-not $item.DfsnAvailable) {
                        [PSWinOpsHealthStatus]::RoleUnavailable
                    }
                    elseif ($item.QueryError) {
                        [PSWinOpsHealthStatus]::Critical
                    }
                    elseif ($item.RootPath -eq 'N/A') {
                        [PSWinOpsHealthStatus]::Healthy
                    }
                    elseif ($item.TargetCount -eq 0 -or $item.HealthyTargets -eq 0) {
                        [PSWinOpsHealthStatus]::Critical
                    }
                    elseif ($item.HealthyTargets -lt $item.TargetCount) {
                        [PSWinOpsHealthStatus]::Degraded
                    }
                    else {
                        [PSWinOpsHealthStatus]::Healthy
                    }

                    [PSCustomObject]@{
                        PSTypeName     = 'PSWinOps.DfsNamespaceHealth'
                        ComputerName   = $displayName
                        ServiceName    = 'Dfs'
                        ServiceStatus  = $item.ServiceStatus
                        RootPath       = $item.RootPath
                        RootType       = $item.RootType
                        State          = $item.State
                        TargetCount    = [int]$item.TargetCount
                        HealthyTargets = [int]$item.HealthyTargets
                        OverallHealth  = $healthStatus
                        Timestamp      = Get-Date -Format 'o'
                    }
                }
            }
            catch {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${machine}': $_"
                continue
            }
        }
    }

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