Public/Sync-LinuxInventory.ps1

function Sync-LinuxInventory {
    <#
    .SYNOPSIS
        Pings all registered Linux servers and reports their online/offline status.
 
    .DESCRIPTION
        Queries the Linux server OU and pings each registered server to check
        connectivity. Optionally updates each server's AD description with a
        last-seen or offline-since timestamp.
 
        This provides a lightweight, agent-free health check that works through
        most firewalls since it uses ICMP (ping).
 
    .PARAMETER OrganizationalUnit
        OU to query. Defaults to "OU=Linux Servers" under the domain root.
 
    .PARAMETER TimeoutMs
        Ping timeout in milliseconds. Defaults to 1000.
 
    .PARAMETER UpdateDescription
        When specified, appends a timestamp tag to the AD description:
        - Online: "[Last Seen: yyyy-MM-dd HH:mm]"
        - Offline: "[OFFLINE since: yyyy-MM-dd HH:mm]"
 
    .EXAMPLE
        Sync-LinuxInventory
 
        Pings all Linux servers and returns online/offline status.
 
    .EXAMPLE
        Sync-LinuxInventory -UpdateDescription
 
        Pings all servers and stamps their AD description with the result.
 
    .EXAMPLE
        Sync-LinuxInventory -TimeoutMs 2000 | Where-Object { -not $_.Online }
 
        Returns only offline servers with a 2-second timeout.
 
    .EXAMPLE
        Sync-LinuxInventory | Format-Table Name, IPAddress, Online, ResponseTime
 
        Quick table view of connectivity status.
 
    .NOTES
        Requires: ActiveDirectory module (RSAT).
        ICMP must be allowed through the host firewall for ping to succeed.
    #>

    [CmdletBinding()]
    param(
        [string]$OrganizationalUnit,

        [int]$TimeoutMs = 1000,

        [switch]$UpdateDescription
    )

    begin {
        Import-Module ActiveDirectory -ErrorAction Stop

        if (-not $OrganizationalUnit) {
            $domainDN = (Get-ADDomain).DistinguishedName
            $OrganizationalUnit = "OU=Linux Servers,$domainDN"
        }

        $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm'
    }

    process {
        # Verify the OU exists
        try {
            $null = Get-ADOrganizationalUnit -Identity $OrganizationalUnit -ErrorAction Stop
        }
        catch {
            Write-Warning "OU not found: $OrganizationalUnit. No Linux servers registered yet."
            return
        }

        $computers = Get-ADComputer -SearchBase $OrganizationalUnit -Filter * `
            -Properties IPv4Address, DNSHostName, Description, OperatingSystem `
            -ErrorAction Stop

        if (-not $computers) {
            Write-Verbose "No computer objects found in $OrganizationalUnit"
            return
        }

        $results = [System.Collections.Generic.List[PSCustomObject]]::new()
        $onlineCount  = 0
        $offlineCount = 0

        foreach ($comp in $computers) {
            $target = if ($comp.IPv4Address) { $comp.IPv4Address } else { $comp.DNSHostName }
            $isOnline     = $false
            $responseTime = $null

            if ($target) {
                Write-Verbose "Pinging $($comp.Name) ($target) with ${TimeoutMs}ms timeout..."
                try {
                    # PowerShell 5.1 compatible: Test-Connection with -Count 1
                    $pingResult = Test-Connection -ComputerName $target -Count 1 -ErrorAction SilentlyContinue
                    if ($pingResult) {
                        $isOnline = $true
                        $responseTime = if ($pingResult.ResponseTime) { $pingResult.ResponseTime } else { $pingResult.Latency }
                    }
                }
                catch {
                    Write-Verbose "Ping failed for $($comp.Name): $_"
                }
            }
            else {
                Write-Verbose "No IP or DNS name for $($comp.Name) -- marking offline."
            }

            if ($isOnline) { $onlineCount++ } else { $offlineCount++ }

            # Update the AD description with timestamp if requested
            if ($UpdateDescription) {
                $currentDesc = $comp.Description
                # Strip any existing timestamp tag
                $cleanDesc = if ($currentDesc) {
                    ($currentDesc -replace '\s*\[(Last Seen|OFFLINE since):.*?\]\s*$', '').Trim()
                } else { '' }

                $tag = if ($isOnline) {
                    "[Last Seen: $timestamp]"
                }
                else {
                    "[OFFLINE since: $timestamp]"
                }

                $newDesc = if ($cleanDesc) { "$cleanDesc $tag" } else { $tag }

                try {
                    Set-ADComputer -Identity $comp.Name -Description $newDesc -ErrorAction Stop
                    Write-Verbose "Updated description for $($comp.Name): $tag"
                }
                catch {
                    Write-Warning "Failed to update description for $($comp.Name): $_"
                }
            }

            $results.Add([PSCustomObject]@{
                Name         = $comp.Name
                IPAddress    = $comp.IPv4Address
                Online       = $isOnline
                ResponseTime = $responseTime
                LastSeen     = if ($isOnline) { $timestamp } else { $null }
            })
        }

        Write-Verbose "Sync complete -- Online: $onlineCount, Offline: $offlineCount"
        $results
    }
}