Disclaimer: I do not accept responsibility for any issues arising from scripts being run without adequate understanding. It is the user's responsibility to review and assess any code before execution. More information

Remote Network Diagnostics with PowerShell and Toast Notifications

If you need a way to proactively monitor network performance for users on mobile connections. The challenge was clear: how do I diagnose slow connections without relying on users to report issues, and how do I inform them when their connection is degraded?

The solution I developed is a PowerShell script that pings multiple servers, logs detailed diagnostics to a network share, and optionally displays user-friendly toast notifications when performance degrades. This approach provides both silent monitoring for IT and real-time feedback for end users.


The Problem

Remote workers using mobile connections often experience degraded performance, but they may not realize their connection is the bottleneck. I needed a system that could:

  1. Monitor network performance to specific servers continuously
  2. Log diagnostic data to a centralized location for troubleshooting
  3. Alert users in a non-intrusive way when their connection is slow
  4. Run without elevation in the user context
  5. Be deployable via enterprise management tools like Absolute

My Solution

The script I created has three main components:

1. Multi-Server Ping Testing

Rather than testing a single endpoint, I configured the script to test multiple servers. This gives us better insight into whether the issue is with the user's connection or a specific server:

$ServersToPing = @("BearVPNGateway", "BearAppSrv1", "BearDC1")
$ThresholdMs = 800  # Customizable threshold in milliseconds

The script pings each server four times and calculates average and maximum response times. This approach helps filter out occasional spikes and identifies genuine connection issues.

2. Centralized Logging

Every execution writes a detailed log to a network share, creating a historical record of network performance:

$NetworkSharePath = "\\BearDiagSrv1\ToastResults$"
$Timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss"
$LogFileName = "${ComputerName}_${Timestamp}.log"

The log files are automatically named with the computer name and timestamp, making it easy to track performance over time and identify patterns. Here's what a typical log entry looks like:

============================================================
Network Performance Log
Computer: LAPTOP-ABC123
Target Servers: BearVPNGateway,BearAppSrv1,BearDC1
Timestamp: 2025-10-04 14:23:15
Threshold: 800 ms
============================================================

Server: st1w3612
------------------------------------------------------------
  Response Time: 245 ms [OK]
  Response Time: 267 ms [OK]
  Response Time: 234 ms [OK]
  Response Time: 251 ms [OK]
  Average: 249.25 ms
  Maximum: 267 ms

3. Toast Notifications for User Awareness

The most user-facing feature is the Windows toast notification. When performance degrades, users receive a friendly, non-intrusive notification:

function Show-ToastNotification {
    param (
        [string]$Title,
        [string]$Message
    )
    
    # Load required assemblies for toast notifications
    [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
    [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null

    # Define the toast notification XML
    $ToastXml = @"
<toast>
    <visual>
        <binding template="ToastGeneric">
            <text>$Title</text>
            <text>$Message</text>
        </binding>
    </visual>
    <audio src="ms-winsoundevent:Notification.Default" />
</toast>
"@

    $XmlDoc = New-Object Windows.Data.Xml.Dom.XmlDocument
    $XmlDoc.LoadXml($ToastXml)
    $AppId = "Microsoft.Explorer.Notification.Default"
    $Toast = [Windows.UI.Notifications.ToastNotification]::new($XmlDoc)
    [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($AppId).Show($Toast)
}

The toast notification uses the Windows Runtime APIs to create native Windows 10/11 notifications. The message I configured guides users toward better connectivity options:

"A slow and unstable mobile signal has been detected and performance may be degraded. If possible, please join either Wi-Fi or Ethernet"

Two Versions: With and Without Notifications

I developed two versions of this script depending on deployment needs:

Version 1: With Toast Notifications (Interactive)

This version is ideal when you want users to be aware of connection issues. The notification appears in the Action Center and provides actionable guidance. It's perfect for environments where users have alternative connection options available.

Version 2: Silent Monitoring (No Notifications)

For environments where you want pure diagnostics without user interaction, I created a version that removes the toast notification entirely while maintaining all diagnostic capabilities.

How the Threshold Detection Works

The script evaluates performance using a simple algorithm:

# Check if threshold exceeded for this server
if ($MaxResponseTime -gt $ThresholdMs) {
    $ThresholdExceeded = $true
    $SlowServers += $server
    Write-Host "  ** SLOW CONNECTION DETECTED **" -ForegroundColor Red
}

I chose to trigger on the maximum response time rather than average because:

  • It catches intermittent connection issues
  • Users experience slowness based on worst-case latency, not average
  • It's more sensitive to degraded connections that might otherwise be masked by a few good pings

The 800ms threshold is configurable and was chosen based on typical application timeout expectations. You can adjust this based on your specific application requirements.

Deploying via Remotely

One of my key requirements was easy deployment through Absolute Secure Access. The script is designed to run with user permissions—no elevation required. This is critical because:

  1. Toast notifications must run in user context to display properly
  2. Network share access uses the user's credentials
  3. No UAC prompts interrupt the user experience

When deploying through Absolute, the script executes seamlessly in the background, tests connectivity, logs results, and optionally notifies the user—all without any administrative overhead.

Scripts : The Complete Scripts

Version 1: With Toast Notifications

# Network Performance Monitor for Absolute Secure Access
# Pings multiple servers and notifies users of slow connections via toast notification

#region Configuration
$ServersToPing = @("BearVPNGateway", "BearAppSrv1", "BearDC1")  # Add or remove servers as needed
$ThresholdMs = 800  # Customizable threshold in milliseconds
$NetworkSharePath = "\\smbcollector.bear.local\ToastOutput$"
#endregion

#region Functions
function Show-ToastNotification {
    param (
        [string]$Title,
        [string]$Message
    )
    
    try {
        # Load required assemblies for toast notifications
        [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
        [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null

        # Define the toast notification XML
        $ToastXml = @"
<toast>
    <visual>
        <binding template="ToastGeneric">
            <text>$Title</text>
            <text>$Message</text>
        </binding>
    </visual>
    <audio src="ms-winsoundevent:Notification.Default" />
</toast>
"@

        # Load the XML
        $XmlDoc = New-Object Windows.Data.Xml.Dom.XmlDocument
        $XmlDoc.LoadXml($ToastXml)

        # Create and show the toast notification
        $AppId = "Microsoft.Explorer.Notification.Default"
        $Toast = [Windows.UI.Notifications.ToastNotification]::new($XmlDoc)
        [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($AppId).Show($Toast)
        
        return $true
    }
    catch {
        Write-Warning "Failed to show toast notification: $_"
        return $false
    }
}

function Test-ServerPing {
    param (
        [string]$Server,
        [int]$Count = 4
    )
    
    try {
        $PingResults = Test-Connection -ComputerName $Server -Count $Count -ErrorAction Stop
        return $PingResults
    }
    catch {
        Write-Warning "Failed to ping server $Server : $_"
        return $null
    }
}

function Write-LogFile {
    param (
        [string]$LogPath,
        [hashtable]$AllPingResults,
        [string]$ComputerName,
        [bool]$ThresholdExceeded,
        [int]$Threshold,
        [array]$ServersTestedArray
    )
    
    try {
        $LogContent = @()
        $LogContent += "="*60
        $LogContent += "Network Performance Log"
        $LogContent += "Computer: $ComputerName"
        $LogContent += "Target Servers: $($ServersTestedArray -join ', ')"
        $LogContent += "Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
        $LogContent += "Threshold: $Threshold ms"
        $LogContent += "="*60
        $LogContent += ""
        
        foreach ($server in $ServersTestedArray) {
            $LogContent += "Server: $server"
            $LogContent += "-"*60
            
            if ($AllPingResults[$server]) {
                $PingResults = $AllPingResults[$server]
                
                foreach ($result in $PingResults) {
                    $responseTime = $result.ResponseTime
                    $status = if ($responseTime -gt $Threshold) { "SLOW" } else { "OK" }
                    $LogContent += "  Response Time: $responseTime ms [$status]"
                }
                
                $AvgResponseTime = ($PingResults | Measure-Object -Property ResponseTime -Average).Average
                $MaxResponseTime = ($PingResults | Measure-Object -Property ResponseTime -Maximum).Maximum
                $LogContent += "  Average: $([math]::Round($AvgResponseTime, 2)) ms"
                $LogContent += "  Maximum: $MaxResponseTime ms"
            }
            else {
                $LogContent += "  ERROR: Unable to ping server"
            }
            
            $LogContent += ""
        }
        
        if ($ThresholdExceeded) {
            $LogContent += "*** WARNING: Threshold exceeded on one or more servers - User notified ***"
            $LogContent += ""
        }
        
        $LogContent += "="*60
        
        # Write to log file
        $LogContent | Out-File -FilePath $LogPath -Encoding UTF8 -Force
        Write-Host "Log file created: $LogPath" -ForegroundColor Green
    }
    catch {
        Write-Warning "Failed to write log file: $_"
    }
}
#endregion

#region Main Script
try {
    # Get computer name
    $ComputerName = $env:COMPUTERNAME
    
    # Create log filename with computer name and timestamp
    $Timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss"
    $LogFileName = "${ComputerName}_${Timestamp}.log"
    $LogFilePath = Join-Path -Path $NetworkSharePath -ChildPath $LogFileName
    
    Write-Host "Starting network performance check..." -ForegroundColor Cyan
    Write-Host "Target Servers: $($ServersToPing -join ', ')" -ForegroundColor Cyan
    Write-Host "Threshold: $ThresholdMs ms" -ForegroundColor Cyan
    Write-Host ""
    
    # Store all ping results
    $AllPingResults = @{}
    $ThresholdExceeded = $false
    $SlowServers = @()
    
    # Ping each server
    foreach ($server in $ServersToPing) {
        Write-Host "Pinging $server..." -ForegroundColor Yellow
        
        $PingResults = Test-ServerPing -Server $server -Count 4
        
        if ($PingResults) {
            $AllPingResults[$server] = $PingResults
            
            # Calculate metrics
            $AvgResponseTime = ($PingResults | Measure-Object -Property ResponseTime -Average).Average
            $MaxResponseTime = ($PingResults | Measure-Object -Property ResponseTime -Maximum).Maximum
            
            Write-Host "  Average: $([math]::Round($AvgResponseTime, 2)) ms | Maximum: $MaxResponseTime ms" -ForegroundColor White
            
            # Check if threshold exceeded for this server
            if ($MaxResponseTime -gt $ThresholdMs) {
                $ThresholdExceeded = $true
                $SlowServers += $server
                Write-Host "  ** SLOW CONNECTION DETECTED **" -ForegroundColor Red
            }
            else {
                Write-Host "  OK" -ForegroundColor Green
            }
        }
        else {
            Write-Host "  Failed to ping server" -ForegroundColor Red
            $AllPingResults[$server] = $null
        }
        
        Write-Host ""
    }
    
    # Show notification if any server exceeded threshold
    if ($ThresholdExceeded) {
        Write-Host "Threshold exceeded on server(s): $($SlowServers -join ', ')" -ForegroundColor Red
        Write-Host "Showing notification..." -ForegroundColor Red
        
        # Show toast notification
        $NotificationShown = Show-ToastNotification `
            -Title "Slow Mobile Connection Detected" `
            -Message "A slow and unstable mobile signal has been detected and performance may be degraded. If possible, please join either Wi-Fi or user an Ethernet cable."
        
        if ($NotificationShown) {
            Write-Host "Toast notification displayed successfully" -ForegroundColor Green
        }
    }
    else {
        Write-Host "All servers are performing within acceptable limits" -ForegroundColor Green
    }
    
    # Write log file
    Write-LogFile -LogPath $LogFilePath -AllPingResults $AllPingResults -ComputerName $ComputerName -ThresholdExceeded $ThresholdExceeded -Threshold $ThresholdMs -ServersTestedArray $ServersToPing
}
catch {
    Write-Error "Script execution failed: $_"
    exit 1
}
#endregion

Version 2: Silent Monitoring (No Toast Notifications)

# Network Performance Monitor for Absolute Secure Access
# Pings multiple servers and logs results to network share

#region Configuration
$ServersToPing = @("BearVPNGateway", "BearAppSrv1", "BearDC1")  # Add or remove servers as needed
$ThresholdMs = 800  # Customizable threshold in milliseconds
$NetworkSharePath = "\\smbcollector.bear.local\ToastOutput$"
#endregion

#region Functions
function Test-ServerPing {
    param (
        [string]$Server,
        [int]$Count = 4
    )
    
    try {
        $PingResults = Test-Connection -ComputerName $Server -Count $Count -ErrorAction Stop
        return $PingResults
    }
    catch {
        Write-Warning "Failed to ping server $Server : $_"
        return $null
    }
}

function Write-LogFile {
    param (
        [string]$LogPath,
        [hashtable]$AllPingResults,
        [string]$ComputerName,
        [bool]$ThresholdExceeded,
        [int]$Threshold,
        [array]$ServersTestedArray
    )
    
    try {
        $LogContent = @()
        $LogContent += "="*60
        $LogContent += "Network Performance Log"
        $LogContent += "Computer: $ComputerName"
        $LogContent += "Target Servers: $($ServersTestedArray -join ', ')"
        $LogContent += "Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
        $LogContent += "Threshold: $Threshold ms"
        $LogContent += "="*60
        $LogContent += ""
        
        foreach ($server in $ServersTestedArray) {
            $LogContent += "Server: $server"
            $LogContent += "-"*60
            
            if ($AllPingResults[$server]) {
                $PingResults = $AllPingResults[$server]
                
                foreach ($result in $PingResults) {
                    $responseTime = $result.ResponseTime
                    $status = if ($responseTime -gt $Threshold) { "SLOW" } else { "OK" }
                    $LogContent += "  Response Time: $responseTime ms [$status]"
                }
                
                $AvgResponseTime = ($PingResults | Measure-Object -Property ResponseTime -Average).Average
                $MaxResponseTime = ($PingResults | Measure-Object -Property ResponseTime -Maximum).Maximum
                $LogContent += "  Average: $([math]::Round($AvgResponseTime, 2)) ms"
                $LogContent += "  Maximum: $MaxResponseTime ms"
            }
            else {
                $LogContent += "  ERROR: Unable to ping server"
            }
            
            $LogContent += ""
        }
        
        if ($ThresholdExceeded) {
            $LogContent += "*** WARNING: Threshold exceeded on one or more servers ***"
            $LogContent += ""
        }
        
        $LogContent += "="*60
        
        # Write to log file
        $LogContent | Out-File -FilePath $LogPath -Encoding UTF8 -Force
        Write-Host "Log file created: $LogPath" -ForegroundColor Green
    }
    catch {
        Write-Warning "Failed to write log file: $_"
    }
}
#endregion

#region Main Script
try {
    # Get computer name
    $ComputerName = $env:COMPUTERNAME
    
    # Create log filename with computer name and timestamp
    $Timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss"
    $LogFileName = "${ComputerName}_${Timestamp}.log"
    $LogFilePath = Join-Path -Path $NetworkSharePath -ChildPath $LogFileName
    
    Write-Host "Starting network performance check..." -ForegroundColor Cyan
    Write-Host "Target Servers: $($ServersToPing -join ', ')" -ForegroundColor Cyan
    Write-Host "Threshold: $ThresholdMs ms" -ForegroundColor Cyan
    Write-Host ""
    
    # Store all ping results
    $AllPingResults = @{}
    $ThresholdExceeded = $false
    $SlowServers = @()
    
    # Ping each server
    foreach ($server in $ServersToPing) {
        Write-Host "Pinging $server..." -ForegroundColor Yellow
        
        $PingResults = Test-ServerPing -Server $server -Count 4
        
        if ($PingResults) {
            $AllPingResults[$server] = $PingResults
            
            # Calculate metrics
            $AvgResponseTime = ($PingResults | Measure-Object -Property ResponseTime -Average).Average
            $MaxResponseTime = ($PingResults | Measure-Object -Property ResponseTime -Maximum).Maximum
            
            Write-Host "  Average: $([math]::Round($AvgResponseTime, 2)) ms | Maximum: $MaxResponseTime ms" -ForegroundColor White
            
            # Check if threshold exceeded for this server
            if ($MaxResponseTime -gt $ThresholdMs) {
                $ThresholdExceeded = $true
                $SlowServers += $server
                Write-Host "  ** SLOW CONNECTION DETECTED **" -ForegroundColor Red
            }
            else {
                Write-Host "  OK" -ForegroundColor Green
            }
        }
        else {
            Write-Host "  Failed to ping server" -ForegroundColor Red
            $AllPingResults[$server] = $null
        }
        
        Write-Host ""
    }
    
    # Display summary if threshold exceeded
    if ($ThresholdExceeded) {
        Write-Host "Threshold exceeded on server(s): $($SlowServers -join ', ')" -ForegroundColor Red
    }
    else {
        Write-Host "All servers are performing within acceptable limits" -ForegroundColor Green
    }
    
    # Write log file
    Write-LogFile -LogPath $LogFilePath -AllPingResults $AllPingResults -ComputerName $ComputerName -ThresholdExceeded $ThresholdExceeded -Threshold $ThresholdMs -ServersTestedArray $ServersToPing
}
catch {
    Write-Error "Script execution failed: $_"
    exit 1
}
#endregion

Conclusion

This PowerShell-based remote diagnostics solution demonstrates how custom scripts to provide better visibility and user experience. The combination of centralized logging and optional toast notifications creates a powerful diagnostic tool that benefits both IT teams and end users..

Previous Post Next Post

نموذج الاتصال