[Guide] Uptime monitoring in Windows
Disclaimer: This is for folks who are running services on Windows machines and does not have more than one device. I am neither an expert at self hosting nor PowerShell. I curated most of this code by doing a lot of “Google-ing” and testing over the years. Feel free to correct any mistakes I have in the code.
Background
TLDR: Windows user needs an uptime monitoring solution
Whenever I searched for uptime monitoring apps, most of the ones that showed up were either hosted on Linux or containers and all I wanted was a a simple exe installation file for some app that will send me alerts when a service or the computer was down. Unfortunately, I couldn’t find anything. If you know one, feel free to recommend them.
To get uptime monitoring on Windows, I had to turn to scripting along with a hosted solution (because you shouldn’t host the monitoring service on the same device as where your apps are running in case the machine goes down). I searched and tested a lot of code to finally end up with the following.
Now, I have services running on both Windows and Linux and I use Uptime Kuma and the following code for monitoring. But, for people who are still on Windows and haven’t made the jump to Linux/containers, you could use these scripts to monitor your services with the same device.
Solution
TLDR: A PowerShell script would check the services/processes/URLs/ports and ping the hosted solution to send out notification.
What I came up with is a PowerShell script that would run every 5 minutes (your preference) using Windows Task Scheduler to check if a Service/Process/URL/Port is up or down and send a ping to Healthchecks.io accordingly.
Prereqs
-
Sign up on healthchecks.io and create a project
-
Add integration to your favorite notification method (There are several options; I use Telegram)
-
Add a Check on Healthchecks.io for each of the service you want to monitor. Ex:
Radarr, Bazarr, Jellyfin
When creating the check, make sure to remember the Slug you used (custom or autogenerated) for that service.
-
Install latest version of PowerShell 7
-
Create a PowerShell file in your desired location. Ex:
healthcheck.ps1
in the C drive -
Go to project settings on Healthchecks.io, get the
Ping key
, and assign it to a variable in the scriptEx:
$HC= “https://hc-ping.com/<YOUR_PING_KEY>/”
The Ping key is used for pinging Healthchecks.io based on the status of the service.
Code
- There are two ways you can write the code: Either check one service or loop through a list.
Port
- To monitor a list of ports, we need to add them to the
Services.csv
file. > The names of the services need to match the Slug you created earlier because, Healthchecks.io uses that to figure out which Check to ping.
Ex:
<span style="color:#323232;">"Service", "Port"
</span><span style="color:#323232;">"qbittorrent", "5656"
</span><span style="color:#323232;">"radarr", "7878"
</span><span style="color:#323232;">"sonarr", "8989"
</span><span style="color:#323232;">"prowlarr", "9696"
</span>
- Then copy the following code to
healthcheck.ps1
:
<span style="color:#323232;">Import-CSV C:Services.csv | foreach{
</span><span style="color:#323232;"> Write-Output ""
</span><span style="color:#323232;"> Write-Output $($_.Service)
</span><span style="color:#323232;"> Write-Output "------------------------"
</span><span style="color:#323232;"> $RESPONSE = Test-Connection localhost -TcpPort $($_.Port)
</span><span style="color:#323232;"> if ($RESPONSE -eq "True") {
</span><span style="color:#323232;"> Write-Host "$($_.Service) is running"
</span><span style="color:#323232;"> curl $HC$($_.Service)
</span><span style="color:#323232;"> } else {
</span><span style="color:#323232;"> Write-Host "$($_.Service) is not running"
</span><span style="color:#323232;"> curl $HC$($_.Service)/fail
</span><span style="color:#323232;"> }
</span><span style="color:#323232;">}
</span>
The script looks through the Services.csv file (Line 1) and check if each of those ports are listening (
$($_.Port)
on Line 5) and pings Healthchecks.io (Line 8 or 11) based on their status with their appropriate name ($($_.Service)
). If the port is not listening, it will ping the URL with a trailing/fail
(Line 11) to indicate it is down.
Service
-
The following code is to check if a service is running.
You can add more services on line 1 in comma separated values. Ex:
@(“bazarr”,“flaresolverr”)
This also needs to match the Slug.
<span style="color:#323232;">$SERVICES = @("bazarr")
</span><span style="color:#323232;">foreach($SERVICE in $SERVICES) {
</span><span style="color:#323232;"> Write-Output ""
</span><span style="color:#323232;"> Write-Output $SERVICE
</span><span style="color:#323232;"> Write-Output "------------------------"
</span><span style="color:#323232;"> $RESPONSE = Get-Service $SERVICE | Select-Object Status
</span><span style="color:#323232;"> if ($RESPONSE.Status -eq "Running") {
</span><span style="color:#323232;"> Write-Host "$SERVICE is running"
</span><span style="color:#323232;"> curl $HC$SERVICE
</span><span style="color:#323232;"> } else {
</span><span style="color:#323232;"> Write-Host "$SERVICE is not running"
</span><span style="color:#323232;"> curl $HC$SERVICE/fail
</span><span style="color:#323232;"> }
</span><span style="color:#323232;">}
</span>
The script looks through the list of services (Line 1) and check if each of those are running (Line 6) and pings Healthchecks.io based on their status.
Process
-
The following code is to check if a process is running.
Line 1 needs to match their Slug
<span style="color:#323232;">$PROCESSES = @("tautulli","jellyfin")
</span><span style="color:#323232;">foreach($PROCESS in $PROCESSES) {
</span><span style="color:#323232;"> Write-Output ""
</span><span style="color:#323232;"> Write-Output $PROCESS
</span><span style="color:#323232;"> Write-Output "------------------------"
</span><span style="color:#323232;"> $RESPONSE = Get-Process -Name $PROCESS -ErrorAction SilentlyContinue
</span><span style="color:#323232;"> if ($RESPONSE -eq $null) {
</span><span style="color:#323232;"> # Write-Host "$PROCESS is not running"
</span><span style="color:#323232;"> curl $HC$PROCESS/fail
</span><span style="color:#323232;"> } else {
</span><span style="color:#323232;"> # Write-Host "$PROCESS is running"
</span><span style="color:#323232;"> curl $HC$PROCESS
</span><span style="color:#323232;"> }
</span><span style="color:#323232;">}
</span>
URL
-
This can be used to check if a URL is responding.
Line 1 needs to match the Slug
<span style="color:#323232;">$WEBSVC = "google"
</span><span style="color:#323232;">$GOOGLE = "https://google.com"
</span><span style="color:#323232;">Write-Output ""
</span><span style="color:#323232;">Write-Output $WEBSVC
</span><span style="color:#323232;">Write-Output "------------------------"
</span><span style="color:#323232;">$RESPONSE = Invoke-WebRequest -URI $GOOGLE -SkipCertificateCheck
</span><span style="color:#323232;">if ($RESPONSE.StatusCode -eq 200) {
</span><span style="color:#323232;"> # Write-Host "$WEBSVC is running"
</span><span style="color:#323232;"> curl $HC$WEBSVC
</span><span style="color:#323232;">} else {
</span><span style="color:#323232;"> # Write-Host "$WEBSVC is not running"
</span><span style="color:#323232;"> curl $HC$WEBSVC/fail
</span><span style="color:#323232;">}
</span>
Ping other machines
-
If you have more than one machine and you want to check their status with the Windows host, you can check it by pinging them
-
Here also I use a CSV file to list the machines. Make sure the server names matches their Slug
Ex:
<span style="color:#323232;">"Server", "IP" </span><span style="color:#323232;">"server2", "192.168.0.202" </span><span style="color:#323232;">"server3", "192.168.0.203" </span>
<span style="color:#323232;">Import-CSV C:Servers.csv | foreach{
</span><span style="color:#323232;"> Write-Output ""
</span><span style="color:#323232;"> Write-Output $($_.Server)
</span><span style="color:#323232;"> Write-Output "------------------------"
</span><span style="color:#323232;"> $RESPONSE = Test-Connection $($_.IP) -Count 1 | Select-Object Status
</span><span style="color:#323232;"> if ($RESPONSE.Status -eq "Success") {
</span><span style="color:#323232;"> # Write-Host "$($_.Server) is running"
</span><span style="color:#323232;"> curl $HC$($_.Server)
</span><span style="color:#323232;"> } else {
</span><span style="color:#323232;"> # Write-Host "$($_.Server) is not running"
</span><span style="color:#323232;"> curl $HC$($_.Server)/fail
</span><span style="color:#323232;"> }
</span><span style="color:#323232;">}
</span>
Task Scheduler
For the script to execute in intervals, you need to create a scheduled task.
- Open Task Scheduler, navigate to the Library, and click on
Create Task
on the right - Give it a name. Ex:
Healthcheck
- Choose
Run whether user is logged on or not
- Choose
Hidden
if needed
- Choose
- On Triggers tab, click on New
- Choose
On a schedule
- Choose
One time
and select an older date than your current date - Select
Repeat task every
and choose the desired time and duration. Ex: 5 minutes indefinitely - Select
Enabled
- Choose
- On Actions tab, click on New
- Choose
Start a program
- Add the path to PowerShell 7 in Program:
"C:Program FilesPowerShell7pwsh.exe"
- Point to the script in arguments:
-windowstyle hidden -NoProfile -NoLogo -NonInteractive -ExecutionPolicy Bypass -File C:healthcheck.ps1
- Choose
- Rest of the tabs, you can choose whatever is appropriate for you.
- Hit Ok/Apply and exit
Notification Method
Depending on the integration you chose, set it up using the Healthchecks docs.
I am using Telegram with the following configuration:
<span style="color:#323232;">Name: Telegram
</span><span style="color:#323232;">Execute on "down" events: POST https://api.telegram.org/bot<ID>/sendMessage
</span><span style="color:#323232;">Request Body:
</span><span style="color:#323232;">```
</span><span style="color:#323232;">{
</span><span style="color:#323232;"> "chat_id": "<CHAT ID>",
</span><span style="color:#323232;"> "text": "🔴 $NAME is DOWN",
</span><span style="color:#323232;"> "parse_mode": "HTML",
</span><span style="color:#323232;"> "no_webpage": true
</span><span style="color:#323232;">}
</span><span style="color:#323232;">```
</span><span style="color:#323232;">Request Headers: Content-Type: application/json
</span><span style="color:#323232;">Execute on "up" events: POST https://api.telegram.org/bot<ID>/sendMessage
</span><span style="color:#323232;">Request Body:
</span><span style="color:#323232;">```
</span><span style="color:#323232;">{
</span><span style="color:#323232;">"chat_id": "<CHAT ID>",
</span><span style="color:#323232;">"text": "🟢 $NAME is UP",
</span><span style="color:#323232;">"parse_mode": "HTML",
</span><span style="color:#323232;">"no_webpage": true
</span><span style="color:#323232;">}
</span><span style="color:#323232;">```
</span><span style="color:#323232;">Request Headers: Content-Type: application/json
</span>
Closing
You can monitor up to 20 services for free. You can also selfhost Healthchecks instance (wouldn’t recommend if you only have one machine).
I’ve been wanting to give something back to the community for a while. I hope this is useful to some of you. Please let me know if you have any questions or suggestions. Thank you for reading!