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:
- Monitor network performance to specific servers continuously
- Log diagnostic data to a centralized location for troubleshooting
- Alert users in a non-intrusive way when their connection is slow
- Run without elevation in the user context
- 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:
- Toast notifications must run in user context to display properly
- Network share access uses the user's credentials
- 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..