HARD DISK MONITORING

Add-on: Powershell scripts #2

The following scripts offered by William Rolison (owner of Wonderdog Training, LLC):

1. HD Sentinel PowerShell Script for Gotify

The script gets hard disk status information from Hard Disk Sentinel when an alert triggered and uses Gotify service.

To use it, please copy/paste the following code and save in a file C:\Scripts\HDSentinelGotify.ps1
Then in Hard Disk Sentinel, select Configuration -> Message Settings -> Run external application or batch file and specify the command line:

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Unrestricted -File "C:\Scripts\HDSentinelGotify.ps1"

On Configuration -> Alerts page, it is possible to select which event(s) should trigger: under the Send/display message, it is possible to select (for example) high temperature, low health and so.


# Hard Disk Sentinel Environment Variables
# https://www.hdsentinel.com/help/en/26_c_msg.html

# Test Values
<#
$env:HDS_Host = 'COMPUTER'
$env:HDS_Alert = 'Overheat'
$env:HDS_Disk = "Disk: #06: Samsung SSD 870 EVO 1TB: 45 °C (Overheat);Disk: #07: Samsung SSD 870 EVO 1TB: 47 °C (Overheat)"
$env:HDS_Threshold = 'Temperature threshold setting: 42 °C'
$env:HDS_Health = 'Disk: #00: Samsung SSD 980 PRO 1TB: 88 %;Disk: #01: Sabrent Rocket 4.0 2TB: 100 %'
#>

# Define variables for Gotify server and App token
$GotifyServer = "https://gotify.example.com"
$Apptoken = "your_token"

Function Send-GotifyMessage {
    <#
    .SYNOPSIS
    Sends a message to the Gotify server.

    .PARAMETER GotifyServer
    The URL of the Gotify server.

    .PARAMETER Apptoken
    The application token for authentication.

    .PARAMETER Title
    The title of the message.

    .PARAMETER Message
    The content of the message.

    .PARAMETER Priority
    The priority level of the message (Min, Low, Normal, High).

    .EXAMPLE
    Send-GotifyMessage -GotifyServer "https://gotify.example.com" -Apptoken "your_token" -Title "Alert" -Message "Disk is overheating!" -Priority "High"
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $True, Position = 0)]
        [string]$GotifyServer,
        [Parameter(Mandatory = $True, Position = 1)]
        [string]$Apptoken,
        [Parameter(Mandatory = $True, Position = 2)]
        [string]$Title = $(Read-Host "Enter Title"),
        [Parameter(Mandatory = $True, Position = 3)]
        [string]$Message = $(Read-Host "Enter message"),
        [Parameter(Mandatory = $True, Position = 4)]
        [ValidateSet("Min", "Low", "Normal", "High")]
        [string]$Priority
    )

    # Validate the Gotify server URL
    if (-not [Uri]::IsWellFormedUriString($GotifyServer, [UriKind]::Absolute)) {
        throw "Invalid Gotify server URL."
    }

    # Hash Table setting Urgency keys to an integer value
    $Urgency = @{
        "Min" = 1
        "Low" = 3
        "Normal" = 6
        "High" = 7
    }

    # Set message urgency based on priority
    $Msgurgency = $Urgency[$Priority]

    # Create the message body
    $body = @{
        "title" = $Title
        "message" = $Message
        "priority" = $Msgurgency
    } | ConvertTo-Json

    # Send the message using Invoke-RestMethod with error handling
    try {
        $response = Invoke-RestMethod -Uri "$GotifyServer/message?token=$Apptoken" -Method Post -Body $body -ContentType "application/json; charset=utf-8" -ErrorAction Stop
        Write-Host "Message sent successfully: $($response.id)"
    } catch {
        Write-Host "Failed to send message: $_"
    }
}

# Prepare the message with multi-line formatting
# Assuming $env:HDS_Disk and $env:HDS_Health contain multiple entries separated by a comma or another delimiter
$diskEntries = $env:HDS_Disk -split ';'  # Adjust the delimiter as necessary
$diskList = $diskEntries -join "`n"  # Join entries with a newline character

$healthEntries = $env:HDS_Health -split ';'  # Adjust the delimiter as necessary
$healthList = $healthEntries -join "`n"  # Join entries with a newline character

$messageContent = "$diskList`n`n$env:HDS_Threshold`n`nOverall Disk Health:`n`n$healthList"

# Call the Send-GotifyMessage function to send the prepared message to the Gotify server
Send-GotifyMessage -GotifyServer $GotifyServer -Apptoken $Apptoken -Title "$($env:HDS_Host): $($env:HDS_Alert)" -Message $messageContent -Priority "High"

Example image

Hard Disk Sentinel Pro and Powershell example to use Gotify

2. Use Pushover notification service for alerts triggered in Hard Disk Sentinel

The following script can be used to receive Pushover notifications when any hard disk drive / SSD triggers an alert in Hard Disk Sentinel Pro.
This is an advanced version of the Use Pushover notification service for alerts triggered in Hard Disk Sentinel script.


<#
.SYNOPSIS
  Send a Pushover notification using Hard Disk Sentinel environment variables.

.NOTES
  - Accepts parameter overrides (useful for scheduled tasks).
  - Tries Process -> User -> Machine env scopes.
  - Use -DebugMode to see where values were found.
  - Supports multiple values for HDS_Disk and HDS_Health (formatted nicely).
  - Uses HttpClient with UTF-8 form encoding to avoid mojibake (e.g., °C).
#>

param(
  [string]$HDS_TimeStampParam,
  [string]$HDS_HostParam,
  [string]$HDS_AlertParam,
  [string]$HDS_DiskParam,
  [string]$HDS_ThresholdParam,
  [string]$HDS_HealthParam,
  [switch]$DebugMode
)

# --- Configuration ---
$HDSDateTimeFormat = 'yyyyMMdd HHmmss'
$PushoverApiUri    = 'https://api.pushover.net/1/messages.json'
$deg = [char]0x00B0

# NOTE: Keep secrets out of checked-in scripts - replace these with secure storage in production
$PushoverToken   = 'Token'
$PushoverUserKey = 'UserKey'

# --- Optional test values (comment these out in production) ---
<#
$env:HDS_TimeStamp = '20190220 222023'
$env:HDS_Host      = 'COMPUTER'
$env:HDS_Alert     = 'Overheat'
$env:HDS_Disk      = "Disk: #06: Samsung SSD 870 EVO 1TB: 45 $deg C (Overheat);Disk: #07: Samsung SSD 870 EVO 1TB: 47 $deg C (Overheat)"
$env:HDS_Threshold = "Temperature threshold setting: 42 $deg C"
$env:HDS_Health    = 'Disk: #00: Samsung SSD 980 PRO 1TB: 88 %;Disk: #01: Sabrent Rocket 4.0 2TB: 100 %'
#>

# --- Helpers ---
function Test-UriNot404 {
    param(
        [Parameter(Mandatory)][string]$Uri
    )

    try {
        if ([string]::IsNullOrWhiteSpace($Uri)) {
            Write-Host "Error: URI is null or empty." -ForegroundColor Red
            return $false
        }
        if (-not [System.Uri]::IsWellFormedUriString($Uri, [System.UriKind]::Absolute)) {
            Write-Host "Error: Invalid URI format: '$Uri'." -ForegroundColor Red
            return $false
        }

        Add-Type -AssemblyName System.Net.Http -ErrorAction SilentlyContinue
        $client = [System.Net.Http.HttpClient]::new()
        $request = [System.Net.Http.HttpRequestMessage]::new('HEAD', $Uri)
        $response = $client.SendAsync($request).Result

        if ($response.StatusCode -eq [System.Net.HttpStatusCode]::NotFound) {
            Write-Host "Error: URI returned 404 Not Found: $Uri" -ForegroundColor Red
            return $false
        }

        return $true
    }
    catch {
        Write-Host "Error validating URI '$Uri': $_" -ForegroundColor Red
        return $false
    }
    finally {
        if ($request) { $request.Dispose() }
        if ($client) { $client.Dispose() }
    }
}
function Test-PushoverCredentials {
    param(
        [string]$ApiUri,
        [string]$Token,
        [string]$UserKey
    )

    try {
        Add-Type -AssemblyName System.Net.Http -ErrorAction SilentlyContinue
        $client = [System.Net.Http.HttpClient]::new()

        $form = @{
            token   = $Token
            user    = $UserKey
            message = 'Credential check - ignore this message'
        }

        $pairs = New-Object 'System.Collections.Generic.List[System.Collections.Generic.KeyValuePair[string,string]]'
        foreach ($k in $form.Keys) {
            $pairs.Add([System.Collections.Generic.KeyValuePair[string,string]]::new($k, $form[$k]))
        }

        $content = New-Object System.Net.Http.FormUrlEncodedContent -ArgumentList (,$pairs)
        $response = $client.PostAsync($ApiUri, $content).Result

        if ($response.StatusCode -eq [System.Net.HttpStatusCode]::BadRequest) {
            Write-Host "Error: Pushover credentials (token/user) are invalid or cause HTTP 400 Bad Request." -ForegroundColor Red
            return $false
        }
        elseif (-not $response.IsSuccessStatusCode) {
            $errorBody = $response.Content.ReadAsStringAsync().Result
            Write-Host "Warning: Pushover API returned status $($response.StatusCode): $errorBody" -ForegroundColor Yellow
            return $false
        }

        return $true
    }
    catch {
        Write-Host "Error validating Pushover credentials: $_" -ForegroundColor Red
        return $false
    }
    finally {
        if ($content) { $content.Dispose() }
        if ($client)  { $client.Dispose() }
    }
}
function Get-EnvVar {
    param([string]$Name, [switch]$ShowWhereFound)
    foreach ($scope in 'Process','User','Machine') {
        $val = [Environment]::GetEnvironmentVariable($Name, $scope)
        if (-not [string]::IsNullOrWhiteSpace($val)) {
            if ($ShowWhereFound) { Write-Host "DEBUG: Found $Name in $scope => $val" }
            return $val
        }
    }
    return $null
}

function Resolve-Value {
    param([string]$ParamValue, [string]$EnvName)
    if (-not [string]::IsNullOrWhiteSpace($ParamValue)) { return $ParamValue }
    return Get-EnvVar -Name $EnvName -ShowWhereFound:$DebugMode
}

# Fix common mojibake for degree symbol: remove U+00C2 when it appears before U+00B0
function Fix-DegreeMojibake {
    param([string]$s)
    if ([string]::IsNullOrEmpty($s)) { return $s }
    $c2  = [char]0x00C2
    $deg = [char]0x00B0
    return ($s -replace ([string]$c2 + $deg), [string]$deg)
}

# --- Resolve inputs (params override env) ---
$values = @{
    HDS_TimeStamp = Resolve-Value $HDS_TimeStampParam 'HDS_TimeStamp'
    HDS_Host      = Resolve-Value $HDS_HostParam      'HDS_Host'
    HDS_Alert     = Resolve-Value $HDS_AlertParam     'HDS_Alert'
    HDS_Disk      = Resolve-Value $HDS_DiskParam      'HDS_Disk'
    HDS_Threshold = Resolve-Value $HDS_ThresholdParam 'HDS_Threshold'
    HDS_Health    = Resolve-Value $HDS_HealthParam    'HDS_Health'
}

# --- Validate required values ---
$missing = $values.GetEnumerator() |
    Where-Object { [string]::IsNullOrWhiteSpace($_.Value) } |
    ForEach-Object { $_.Key }

if ($missing.Count -gt 0) {
    Write-Error "Missing environment variables or parameters: $($missing -join ', ')"
    if ($DebugMode) {
        Write-Host "Hint: run `Get-ChildItem Env:` or `[Environment]::GetEnvironmentVariable('HDS_TimeStamp','User')` to inspect values."
    }
    exit 1
}

if (-not (Test-UriNot404 -Uri $PushoverApiUri)) {
    exit 1
}
if (-not (Test-PushoverCredentials -ApiUri $PushoverApiUri -Token $PushoverToken -UserKey $PushoverUserKey)) {
    exit 1
}

# --- Parse timestamp ---
try {
    $HDSDateTime = [DateTime]::ParseExact(
        $values['HDS_TimeStamp'],
        $HDSDateTimeFormat,
        [System.Globalization.CultureInfo]::InvariantCulture
    )
}
catch {
    Write-Error "Failed to parse HDS_TimeStamp ('$($values['HDS_TimeStamp'])') with format $HDSDateTimeFormat. $_"
    exit 1
}

# --- Unix timestamp (portable) ---
$epoch = Get-Date '1970-01-01 00:00:00Z'
$PushoverTimestamp = [math]::Round( ($HDSDateTime.ToUniversalTime() - $epoch).TotalSeconds )

# --- Normalize degree mojibake safely ---
$values['HDS_Disk']      = Fix-DegreeMojibake $values['HDS_Disk']
$values['HDS_Threshold'] = Fix-DegreeMojibake $values['HDS_Threshold']
$values['HDS_Health']    = Fix-DegreeMojibake $values['HDS_Health']

# --- Split multi-value fields on ';' ---
$diskEntries   = ($values['HDS_Disk']   -split ';') | ForEach-Object { $_.Trim() } | Where-Object { $_ }
$healthEntries = ($values['HDS_Health'] -split ';') | ForEach-Object { $_.Trim() } | Where-Object { $_ }

# --- Build message text ---
$messageText = @()
$messageText += "Disk Status:"
$messageText += ($diskEntries -join "`n")
$messageText += ""
$messageText += $values['HDS_Threshold']
$messageText += ""
$messageText += "Overall Disk Health:"
$messageText += ($healthEntries -join "`n")

# --- Prepare request body (all strings) ---
$body = @{
    token     = [string]$PushoverToken
    user      = [string]$PushoverUserKey
    message   = [string]($messageText -join "`n")
    title     = [string]("$($values['HDS_Host']): $($values['HDS_Alert'])")
    timestamp = [string]$PushoverTimestamp
}

# --- Debug output (masked) ---
if ($DebugMode) {
    $masked = $body.Clone()
    if ($masked.ContainsKey('token')) { $masked['token'] = '***masked***' }
    if ($masked.ContainsKey('user'))  { $masked['user']  = '***masked***' }
    Write-Host "DEBUG: Request body (pre-send) ="
    $masked | Format-List
}

# --- Ensure TLS 1.2 for older Windows ---
try {
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
} catch { }

# --- Send via HttpClient using UTF-8 FormUrlEncodedContent ---
Add-Type -AssemblyName System.Net.Http -ErrorAction SilentlyContinue

# Convert hashtable -> List[KeyValuePair[string,string]] for FormUrlEncodedContent
$pairs = New-Object 'System.Collections.Generic.List[System.Collections.Generic.KeyValuePair[string,string]]'
foreach ($k in $body.Keys) {
    $pairs.Add([System.Collections.Generic.KeyValuePair[string,string]]::new($k, [string]$body[$k]))
}

$client  = $null
$content = $null
try {
    $client  = New-Object System.Net.Http.HttpClient
    # IMPORTANT: pass the list as a single argument so PS doesn't expand it into multiple params
    $content = New-Object System.Net.Http.FormUrlEncodedContent -ArgumentList (,$pairs)  # UTF-8 by default
    $response = $client.PostAsync($PushoverApiUri, $content).Result
    if (-not $response.IsSuccessStatusCode) {
        $errBody = $response.Content.ReadAsStringAsync().Result
        throw "HTTP $([int]$response.StatusCode) $($response.ReasonPhrase) - $errBody"
    }
    Write-Host 'Pushover notification sent successfully.' -ForegroundColor Green
}
catch {
    Write-Error "Failed to send Pushover notification: $($_.Exception.Message)"
    exit 1
}
finally {
    if ($content) { $content.Dispose() }
    if ($client)  { $client.Dispose() }
}

Example image

Hard Disk Sentinel Pro and Powershell example to use Pushover notification service

Did you make any scripts, tools, add-ons which may be useful? Please send an e-mail so it can be shared to help other users.

Disclaimer

Heartfelt Development Services Hungary Kft. (HD Sentinel) is providing you with non-supported materials first developed at Heartfelt Development Services Hungary Kft. (HD Sentinel)'s expense which are "Support Materials".

The Support Materials have not been tested by Heartfelt Development Services Hungary Kft. (HD Sentinel) and are not recommended for use in production environments.

Support Materials may have been created using artificial intelligence tools and may contain errors, discrepancies, vulnerabilities or other defects.

Support Materials are provided "as-is" and Heartfelt Development Services Hungary Kft. (HD Sentinel) will not provide any enhancements, bug fixes, updates or other support for the Support Materials.

In addition, the Support Materials are specifically excluded from any warranties, guarantees or other promises provided in your license agreements with Heartfelt Development Services Hungary Kft. (HD Sentinel) or otherwise implied by law.

Heartfelt Development Services Hungary Kft. (HD Sentinel) retains all ownership rights in the Support Materials and hereby grants you a license to copy, use and modify the Support Materials only as needed to assist with your use of the separately-licensed Heartfelt Development Services Hungary Kft. (HD Sentinel) products.

 

Navigation

Hard Disk Sentinel Home

News

Products

Store

Support

Partners

Useful links

Download Hard Disk Sentinel

Frequently asked questions

Knowledge base

Discussion forum

Contact us

Privacy policy

Resources

Q&A Knowledge Base

Testimonials

Use Case

HDSentinel on Facebook

HDD Monitoring Blog

About

Newsletter

Enter your e-mail address to receive news, tips, updates and special offers about Hard Disk Sentinel software. More ...

Your e-mail address is kept securely (see our privacy policy).


© 2026 H.D.S. Hungary. All Rights Reserved.

Software for hard disk monitoring and data protection