The Windmill Club logo

The Windmill Club

Agentic AI for the rest of us — a project of PCSNJ

Windows Network Health Audit

This is where you start to see the creative side of agentic AI. Instead of one fixed script for everyone, this tool looks at your Windows network, shows you what it finds, and then builds a brand-new health-check script custom to you and your devices. A tool that builds a tool.

What it does

It's a three-step process that adapts to each person who runs it:

StepWhat happensWhy it's clever
1. DiscoverScans your Windows network and lists the devices it can see, identifying each one by combining its hostname, manufacturer (MAC address), and the services it responds to.It's looking at your environment — and tells you what each device is, not just its address.
2. AskFor each device, it asks you: "Include this in your health check? (y/n)".You stay in control — you decide what matters to watch.
3. BuildIt writes a brand-new PowerShell script that checks each device you chose, in a way that suits its type.The output is custom-built for your specific network.
The point of this tool is not the health check itself. The point is showing you that an agent can look at your world and adapt — building a tool for your situation, not a one-size-fits-all script. That adaptive, creative capability is what separates agentic AI from a plain search box.
Security — please read.
This script is read-only. It reads your network's list of connected devices (the same list your computer already keeps) and asks you questions. It does not change settings, delete files, download anything, or send anything anywhere. When it builds your custom health-check script, that script simply pings (checks if) the devices you chose. We show the full code below so you can verify every line before you run it. Your computer, your choice.

How to run it

There are two scripts in this tool, and it helps to know the difference before you start:

  1. Open Windows PowerShell. Press the Start menu, type PowerShell, and click it.
  2. Copy this builder script from the box below (use the Copy button).
  3. Paste it into the PowerShell window and press Enter.
  4. Wait for the scan — it reads your device list and probes a few services. It may appear to be stuck or frozen while it gathers device information; that's normal. Be patient and let it finish (usually a minute or two).
  5. Choose how to pick your devices. Type a for Auto (include every device it found — quick) or m for Manual (review each device and answer y/n for each).
  6. It writes a custom health-check script for your devices and opens it in Notepad.
  7. It asks: "Would you like to run it now?" Type y and it runs the check for you automatically. When it finishes, you'll see the finished report on screen, it saves a copy to your Desktop (a file named Windmill_NetworkHealthReport_…), and it opens the report automatically in Notepad. Everything lands on your Desktop — the one place everyone knows how to find.
Tip: Not every device shows up, and some appear without a friendly name (sleeping phones, smart plugs, light bulbs often don't respond). That's normal. Include the ones that matter to you — computers, printers, your router — and skip the rest.

The script

Produced by an agentic AI on request, then reviewed and tested. Here it is in full:

<#
=====================================================================
  The Windmill Club — Windows Network Health Audit Builder
  "Agentic AI for the rest of us — a project of PCSNJ"

  WHAT THIS DOES
  --------------
  This script looks at the Windows network your computer is connected
  to and finds the devices that are visible on it. For each device it
  tries to figure out WHAT it is (router, printer, computer, etc.) by
  combining several honest clues:
     - the device's hostname, if it offers one
     - the manufacturer behind its network address (MAC prefix)
     - what services it responds to (web, print, etc.)

  Then it asks YOU which devices you'd like to keep an eye on. When
  you're done choosing, it writes a brand-new health-check script
  built specifically for YOUR network, checking each device in a way
  that makes sense for its type.

  IS IT SAFE TO RUN?
  ------------------
  Yes. This script only READS information. It does not change, install,
  or send anything anywhere. It looks at your device list, asks a few
  friendly questions, and writes a text/script file. That's all.

  HOW TO RUN IT
  -------------
  1. Open PowerShell (Start menu -> type "PowerShell" -> click it).
  2. Paste this whole script in and press Enter.
  3. Wait while it reads your network (a minute or two).
  4. A list of found devices appears. For each one, type y or n to say
     whether you want it in your health check.
  5. It writes a custom health-check script for YOUR devices and opens
     it so you can read it before running it.

  NOTES
  -----
  - Phones, tablets, and many smart-home devices that are asleep do not
    answer, so they may not be identified beyond their manufacturer.
    That's normal and honest — we label those "unidentified device"
    rather than guess.
  - You can include or skip ANY device. Your choice, every time.
  - This is an introductory example, by design. The checks are simple
    and real. More advanced checks can come later.
=====================================================================
#>

$ErrorActionPreference = "Continue"

function Write-Header {
  Write-Host ""
  Write-Host "========================================================" -ForegroundColor DarkYellow
  Write-Host "  The Windmill Club — Windows Network Health Audit Builder" -ForegroundColor DarkYellow
  Write-Host "  Builds a custom health-check tool for YOUR network" -ForegroundColor DarkYellow
  Write-Host "========================================================" -ForegroundColor DarkYellow
  Write-Host ""
}

function Get-NetworkRange {
  # Find the REAL LAN interface — the one that has a default gateway.
  # This avoids WSL / VPN / virtual adapters (which have no default route).
  try {
    $route = Get-NetRoute -DestinationPrefix "0.0.0.0/0" -ErrorAction SilentlyContinue |
             Where-Object { $_.NextHop -notlike "0.0.0.0" } |
             Sort-Object RouteMetric | Select-Object -First 1
    if ($route -and $route.InterfaceIndex) {
      $ipObj = Get-NetIPAddress -AddressFamily IPv4 -InterfaceIndex $route.InterfaceIndex -ErrorAction SilentlyContinue |
               Where-Object { $_.IPAddress -notlike "169.254.*" } | Select-Object -First 1
      if ($ipObj) {
        $octets = $ipObj.IPAddress -split "\."
        $range = ($octets[0] + "." + $octets[1] + "." + $octets[2] + ".")
        return @{ Range = $range; Gateway = $route.NextHop; OwnIP = $ipObj.IPAddress }
      }
    }
  } catch {}
  # Fallback: pick the first private-range (RFC1918) IPv4 that isn't a virtual adapter
  try {
    $ip = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object {
      $_.IPAddress -match "^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[01])\."
    } | Select-Object -First 1).IPAddress
    if ($ip) {
      $octets = $ip -split "\."
      return @{ Range = ($octets[0] + "." + $octets[1] + "." + $octets[2] + "."); Gateway = ""; OwnIP = $ip }
    }
  } catch {}
  return $null
}

function Get-VendorHint {
  param([string]$oui)
  # A helpful lookup of common vendor prefixes. Not exhaustive — the point is
  # to give a useful hint for the most common home-network devices.
  $map = @{
    # Printers
    "3C2AF4" = "Brother"; "00E02C" = "Lexmark"; "006087" = "HP"
    "3C52A1" = "HP"; "9C8EDC" = "HP"; "000425" = "Ricoh"; "000747" = "Epson"
    "003048" = "Epson"; "08002F" = "Xerox"; "001999" = "Canon"
    # Computers
    "B82A72" = "Dell"; "001150" = "Dell"; "005056" = "VMware"
    "185680" = "Intel"; "000C29" = "VMware"; "080027" = "VMware"
    "C4A3AB" = "Lenovo"; "001C25" = "Lenovo"; "0050B6" = "Cisco"
    "002219" = "Lenovo"; "F88A5E" = "Acer"; "00E0B8" = "Acer"
    # Apple
    "001B78" = "Apple"; "3C22FB" = "Apple"; "0C4BC6" = "Apple"
    "A4C3F0" = "Apple"; "F0D5BF" = "Apple"; "78D752" = "Apple"
    # Samsung / phones / tablets
    "40D3AE" = "Samsung"; "00A0C9" = "Samsung"; "AC5F3E" = "Samsung"
    "38B54A" = "Samsung"; "BCE59D" = "Samsung"
    # Amazon / Echo / Fire
    "007147" = "Amazon"; "6C5697" = "Amazon"; "AC63BE" = "Amazon"
    "4405C7" = "Amazon"; "34D2C5" = "Amazon"; "D4F9A1" = "Amazon"
    # Routers / networking
    "04A222" = "Arcadyan"; "F8E4E3" = "Netgear"; "00C0B7" = "D-Link"
    "00E04C" = "Cisco"; "001A2B" = "Asus"; "DCD24A" = "Linksys"
    "0012EF" = "TP-Link"; "50C7BF" = "TP-Link"; "FCFC48" = "TP-Link"
    "58C1CB" = "TP-Link"; "00E0FC" = "Asus"; "10BF48" = "Netgear"
    # Cameras / smart home
    "C4E984" = "Hikvision"; "00C1B4" = "Hikvision"; "0014A9" = "Hikvision"
    "1C8E5C" = "TP-Link(IoT)"; "CC40D0" = "Wyze"; "2CF4C5" = "Wyze"
    # Google / Nest
    "F4F5D8" = "Google/Nest"; "3C5AB4" = "Google/Nest"; "B4F1DA" = "Google/Nest"
    # LG / misc computers
    "001CBF" = "LG"; "A4EE57" = "LG"; "0007B7" = "LG"
  }
  if ($map.ContainsKey($oui)) { return $map[$oui] }
  return ""
}

function Test-PortAsync {
  param([string]$ip, [int]$port)
  # Start a connection attempt and return an object we can wait on later.
  $client = New-Object System.Net.Sockets.TcpClient
  $async = $client.BeginConnect($ip, $port, $null, $null)
  return @{ Client = $client; Async = $async; Port = $port }
}

function Wait-PortAsync {
  param($pending, [int]$timeoutMs)
  # Wait for each started connection to complete or time out, returning named flags.
  $result = @{ HTTP=$false; HTTPS=$false; Print=$false; IPP=$false; Cam=$false }
  foreach ($p in $pending) {
    $open = $false
    try {
      $open = $p.Async.AsyncWaitHandle.WaitOne($timeoutMs, $false) -and $p.Client.Connected
    } catch {}
    $p.Client.Close()
    switch ($p.Port) {
      80   { $result["HTTP"]  = $open }
      443  { $result["HTTPS"] = $open }
      9100 { $result["Print"] = $open }
      631  { $result["IPP"]   = $open }
      554  { $result["Cam"]   = $open }
    }
  }
  return $result
}

function Get-DeviceType {
  param([string]$vendor, [string]$hostname, [hashtable]$ports, [string]$ip, [string]$gateway)
  # Build an honest identification from the clues we have.
  # Priority: gateway -> hostname -> ports -> vendor.

  # 1. The router is usually the gateway address.
  if ($gateway -and $ip -eq $gateway) { return "Router (gateway)" }

  # 2. A revealing hostname often names the device.
  if ($hostname) {
    $h = $hostname.ToLower()
    if ($h -match "brw" -and $h -match "[0-9a-f]{6}") { return "Printer (Brother)" }
    if ($h -match "hp|laserjet|deskjet|officejet|envy") { return "Printer (HP)" }
    if ($h -match "epson") { return "Printer (Epson)" }
    if ($h -match "canon|mg[0-9]|pixma") { return "Printer (Canon)" }
    if ($h -match "notebook|laptop|desktop|pc|workstation|server|ubuntu|centos") { return "Computer" }
  }

  # 3. What services it answers to.
  if ($ports["Print"] -or $ports["IPP"]) { return "Printer" }
  if ($ports["Cam"] -or $ports["ONVIF"]) { return "Camera" }

  # 4. Vendor hint.
  if ($vendor) {
    switch -Regex ($vendor) {
      "Brother|Lexmark|HP|Epson|Ricoh|Xerox|Canon" { return "Printer" }
      "Dell|Lenovo|Acer|Intel|VMware|LG"           { return "Computer" }
      "Samsung"                                    { return "Samsung device" }
      "Amazon"                                     { return "Amazon device (Echo/Fire)" }
      "Arcadyan|Netgear|D-Link|Cisco|Asus|Linksys|TP-Link" { return "Router / network" }
      "Hikvision|Wyze"                             { return "Camera" }
      "Google/Nest"                                { return "Google/Nest device" }
      "Apple"                                      { return "Apple device" }
    }
  }

  # 5. If it responds to web traffic but nothing else, it's likely a device
  #    with a web interface (camera, printer, or smart device).
  if ($ports["HTTP"] -or $ports["HTTPS"]) { return "Device with web interface" }

  # 6. Nothing reliable to say — be honest rather than guess.
  return "Unidentified device"
}

function Get-DiscoveredDevices {
  param([hashtable]$net)
  $range = $net.Range
  $gateway = $net.Gateway
  $devices = @()
  try {
    $arp = arp -a
    foreach ($line in $arp) {
      if ($line -match '(\d+\.\d+\.\d+\.\d+)\s+([0-9a-fA-F-]{17})') {
        $ip   = $Matches[1]
        $mac  = $Matches[2].ToUpper()
        # Skip broadcast / multicast / non-LAN noise
        if ($ip -eq "255.255.255.255") { continue }
        if ($mac -eq "FF-FF-FF-FF-FF-FF") { continue }
        if ($ip -like "224.*" -or $ip -like "239.*") { continue }
        $firstOctet = [int]($ip -split "\.")[0]
        if ($firstOctet -lt 1 -or $firstOctet -gt 223) { continue }
        if ($ip -like "169.254.*") { continue }
        if ($range -and $ip -notlike "$range*") { continue }
        $devices += [PSCustomObject]@{
          IP     = $ip
          MAC    = $mac
        }
      }
    }
  } catch {}
  $devices = $devices | Sort-Object IP -Unique

  # For each device, gather the identification clues.
  # Port probes run in parallel per device so the scan stays reasonably quick.
  $result = @($devices | ForEach-Object {
    $ip = $_.IP
    $mac = $_.MAC
    $hn = ""
    try {
      $r = [System.Net.Dns]::GetHostEntry($ip)
      if ($r -and $r.HostName -and $r.HostName -notlike "*in-addr*") { $hn = $r.HostName }
    } catch {}
    # Probe the most telling services — all connection attempts start at once,
    # then we wait, so the 5 probes take about the time of a single one.
    $pending = @(
      (Test-PortAsync $ip 80),
      (Test-PortAsync $ip 443),
      (Test-PortAsync $ip 9100),
      (Test-PortAsync $ip 631),
      (Test-PortAsync $ip 554)
    )
    $portTasks = Wait-PortAsync $pending 200
    $oui = ($mac -replace "-", "").Substring(0, 6)
    $vendor = Get-VendorHint $oui
    $type = Get-DeviceType $vendor $hn $portTasks $ip $gateway
    [PSCustomObject]@{
      IP       = $ip
      MAC      = $mac
      Vendor   = if ($vendor) { $vendor } else { "unknown" }
      Type     = $type
      Hostname = if ($hn) { $hn } else { "" }
    }
  })
  return $result
}

function Prompt-ForDevices {
  param($devices)
  $selected = @()
  $total = $devices.Count
  if ($total -eq 0) {
    Write-Host "No devices were found on this scan." -ForegroundColor Yellow
    Write-Host "This can happen if the network is quiet or the scan is limited."
    Write-Host "Try again after using the network a bit, or check that you are on your home network."
    return $selected
  }
  Write-Host "Found $total device(s) on your network." -ForegroundColor Cyan
  Write-Host ""

  # Frank-proofing: let the user choose auto (accept all) or manual (review each).
  $mode = ""
  while ($mode -notmatch "^[am]$") {
    Write-Host "How would you like to choose which devices to include?"
    Write-Host "  a = Auto: include all discovered devices"
    Write-Host "  m = Manual: review each device and pick (y/n)"
    Write-Host "Choice (a/m): " -NoNewline
    $mode = (Read-Host).Trim().ToLower()
  }

  if ($mode -eq "a") {
    # Auto: accept everything the scan found.
    $selected = @($devices)
    Write-Host ""
    Write-Host "Auto mode: including all $total device(s)." -ForegroundColor Green
    return $selected
  }

  # Manual mode: review each device.
  $included = 0
  Write-Host ""
  Write-Host "Manual mode: review each device." -ForegroundColor Cyan
  Write-Host ""
  foreach ($d in $devices) {
    $name = $d.Type
    if ($d.Hostname) { $name = $name + "  [" + $d.Hostname + "]" }
    $answer = ""
    while ($answer -notmatch "^[yn]$") {
      Write-Host ("  {0}  {1}" -f $d.IP, $name) -NoNewline
      Write-Host "  Include? (y/n): " -NoNewline -ForegroundColor Gray
      $answer = (Read-Host).Trim().ToLower()
    }
    if ($answer -eq "y") {
      $selected += $d
      $included++
    }
  }
  Write-Host ""
  Write-Host "Included $included of $total device(s)." -ForegroundColor Green
  return $selected
}

function Write-CustomHealthScript {
  param($devices, [string]$scriptPath)
  $sb = New-Object System.Text.StringBuilder
  [void]$sb.AppendLine("<#")
  [void]$sb.AppendLine("  Custom Network Health Audit")
  [void]$sb.AppendLine("  Generated by The Windmill Club Windows Network Health Audit Builder")
  [void]$sb.AppendLine("  Generated on: " + (Get-Date -Format "yyyy-MM-dd HH:mm"))
  [void]$sb.AppendLine("  Devices checked: " + $devices.Count)
  [void]$sb.AppendLine("#>")
  [void]$sb.AppendLine("")
  # Save the report to the DESKTOP — the one place every user knows how to find.
  [void]$sb.AppendLine('$outFolder = [Environment]::GetFolderPath("Desktop")')
  [void]$sb.AppendLine('if (-not (Test-Path $outFolder)) { $outFolder = (Get-Location).Path }')
  [void]$sb.AppendLine('$reportFile = Join-Path $outFolder ("Windmill_NetworkHealthReport_" + (Get-Date -Format "yyyyMMdd_HHmm") + ".txt")')
  [void]$sb.AppendLine('$report = New-Object System.Collections.Generic.List[string]')
  [void]$sb.AppendLine('Add-Type -AssemblyName System.Collections')
  [void]$sb.AppendLine("")
  [void]$sb.AppendLine('Write-Host ""')
  [void]$sb.AppendLine('Write-Host "========================================" -ForegroundColor DarkYellow')
  [void]$sb.AppendLine('Write-Host "  Custom Network Health Audit" -ForegroundColor DarkYellow')
  [void]$sb.AppendLine('Write-Host "========================================" -ForegroundColor DarkYellow')
  [void]$sb.AppendLine('$report.Add("Custom Network Health Audit")')
  [void]$sb.AppendLine('$report.Add("Generated: " + (Get-Date -Format "yyyy-MM-dd HH:mm"))')
  [void]$sb.AppendLine('$report.Add("========================================")')
  [void]$sb.AppendLine('$report.Add("")')
  [void]$sb.AppendLine('Write-Host ("Checking " + $(' + $devices.Count + ') + " device(s)...")')
  [void]$sb.AppendLine('$script:okCount = 0')
  [void]$sb.AppendLine("")
  foreach ($d in $devices) {
    [void]$sb.AppendLine('$ip = "' + $d.IP + '"')
    [void]$sb.AppendLine('$name = "' + $d.Type + '"')
    [void]$sb.AppendLine('$up = Test-Connection -ComputerName $ip -Count 1 -Quiet -ErrorAction SilentlyContinue')
    [void]$sb.AppendLine('if ($up) {')
    [void]$sb.AppendLine('  Write-Host ("  {0,-15} {1,-28} [OK] reachable" -f $ip, $name) -ForegroundColor Green')
    [void]$sb.AppendLine('  $script:okCount++')
    [void]$sb.AppendLine('  $report.Add(("{0,-15} {1,-28} [OK]  reachable" -f $ip, $name))')
    [void]$sb.AppendLine('} else {')
    [void]$sb.AppendLine('  Write-Host ("  {0,-15} {1,-28} [DOWN] no response" -f $ip, $name) -ForegroundColor Red')
    [void]$sb.AppendLine('  $report.Add(("{0,-15} {1,-28} [DOWN] no response" -f $ip, $name))')
    [void]$sb.AppendLine('}')
    [void]$sb.AppendLine("")
  }
  [void]$sb.AppendLine('$summary = "Checked {0} device(s): {1} reachable, {2} not responding." -f $(' + $devices.Count + '), $script:okCount, ($(' + $devices.Count + ') - $script:okCount)')
  [void]$sb.AppendLine('Write-Host ""')
  [void]$sb.AppendLine('Write-Host "----- FINISHED REPORT -----" -ForegroundColor DarkYellow')
  [void]$sb.AppendLine('$report.Add("")')
  [void]$sb.AppendLine('$report.Add("Summary: " + $summary)')
  [void]$sb.AppendLine('$report.Add("A DOWN device may be asleep, off, or unreachable. Not every silent device is broken.")')
  [void]$sb.AppendLine('$report | Set-Content -Path $reportFile -Encoding UTF8')
  # Show the FULL finished report at the end, all in one block.
  [void]$sb.AppendLine('Get-Content $reportFile | ForEach-Object { Write-Host $_ }')
  [void]$sb.AppendLine('Write-Host ""')
  [void]$sb.AppendLine('Write-Host ("Your report has been saved to your Desktop as: " + (Split-Path $reportFile -Leaf)) -ForegroundColor Green')
  [void]$sb.AppendLine('Write-Host ("Full path: " + $reportFile) -ForegroundColor Gray')
  [void]$sb.AppendLine('Write-Host "Opening it now so you can read it..." -ForegroundColor Cyan')
  [void]$sb.AppendLine('Start-Process notepad -ArgumentList $reportFile')
  [void]$sb.AppendLine('Write-Host ""')
  [void]$sb.AppendLine('Write-Host "Press Enter to close this window..." -NoNewline')
  # Read-Host without a visible prompt so the user just sees the message above.
  [void]$sb.AppendLine('[void](Read-Host -Prompt "")')
  Set-Content -Path $scriptPath -Value $sb.ToString() -Encoding UTF8
  return $scriptPath
}

# ---------- Main ----------
Write-Header

$net = Get-NetworkRange
if (-not $net) {
  Write-Host "Could not determine your network. Are you connected to a network?" -ForegroundColor Red
  Read-Host "Press Enter to exit"
  exit 1
}

Write-Host "Scanning your network ($($net.Range)*) for devices..." -ForegroundColor Cyan
Write-Host "(This reads your device list and probes a few services. It changes nothing.)"
Write-Host ""

$devices = Get-DiscoveredDevices $net
$chosen = Prompt-ForDevices $devices

if ($chosen.Count -eq 0) {
  Write-Host "No devices were selected, so no health-check script was created." -ForegroundColor Yellow
  Read-Host "Press Enter to exit"
  exit 0
}

$scriptPath = Join-Path (Get-Location) ("NetworkHealthAudit_" + (Get-Date -Format "yyyyMMdd_HHmm") + ".ps1")
Write-CustomHealthScript $devices $scriptPath

Write-Host ""
Write-Host "Your custom network health-audit script has been created:" -ForegroundColor Green
Write-Host "  $scriptPath"
Write-Host ""
Write-Host "Opening it in Notepad so you can see what it does..." -ForegroundColor Gray
Start-Process notepad -ArgumentList $scriptPath

# Frank-proofing: don't make the user figure out how to run a .ps1 file.
# Ask if they'd like us to run it for them right now.
Write-Host ""
$runIt = ""
while ($runIt -notmatch "^[yn]$") {
  Write-Host "Would you like to run it now? (y/n): " -NoNewline
  $runIt = (Read-Host).Trim().ToLower()
}
if ($runIt -eq "y") {
  Write-Host ""
  Write-Host "Running your custom health check now..." -ForegroundColor Cyan
  Write-Host ""
  Invoke-Expression (Get-Content $scriptPath -Raw)
} else {
  Write-Host ""
  Write-Host "No problem. To run it later, right-click the file and choose 'Run with PowerShell'." -ForegroundColor Yellow
}

Read-Host "Press Enter to exit"
Why this is different. The first tool (Windows Health Audit) was a fixed script — the same for everyone. This one adapts: it looks at your network, asks you what matters, and produces something unique to you. That's the creative step — an agent that builds a custom tool for a specific environment, rather than handing everyone the same thing.
An honest note about scope. This is an introductory example. It discovers devices, identifies what it reasonably can, and builds a simple reachability health check. Devices that are asleep or don't answer (many phones and smart-home gadgets) are honestly labeled "unidentified device" rather than guessed. More advanced checks — reading a device's status, disk, or uptime — are possible, and we may add them in the future. This version is meant to show the idea: a tool that builds a tool, tailored to you.
← Back to Home