As a Managed Service Provider (MSP) in Boca Raton, FL, automation is at the heart of delivering efficient, reliable IT services to our clients across South Florida. One common challenge we face is keeping monitoring Zabbix Agents up to date. While Zabbix is a powerful open-source monitoring solution, it lacks an evergreen download link for its Windows agent. This means administrators often need to manually check for the latest version, update scripts, and redeploy installers—a time-consuming and error-prone process.
To solve this, we developed a PowerShell function that dynamically retrieves the latest Zabbix Windows Agent 2 MSI from the official CDN and optionally downloads it. This eliminates the need for hardcoding URLs or manually updating scripts whenever a new version is released.
Why This Matters
- Time Savings: No more manual checks or script edits for every update.
- Consistency: Always deploy the latest stable version across your environment.
- Automation-Friendly: Integrates seamlessly into deployment pipelines, RMM tools, or scheduled tasks.
- Error Reduction: Avoid broken links or outdated agents that compromise monitoring accuracy.
For MSPs and IT teams managing multiple endpoints, this approach ensures proactive monitoring without unnecessary overhead.
What is Zabbix and How Do We Use It to Help Businesses?
Zabbix is a powerful, open-source monitoring platform that tracks the health and performance of servers, networks, applications, and cloud services in real time. For businesses, this means early detection of issues before they impact operations, improved uptime, and better resource planning. At our MSP in Boca Raton, we leverage Zabbix as one of our core tools to proactively identify and resolve problems before they disrupt your business. This proactive approach keeps your systems running efficiently, minimizes downtime, and ensures your team stays productive. By combining Zabbix with our automation strategies, we help clients maintain a stable, secure, and high-performing IT environment.
How It Works
The function scans the Zabbix CDN, detects the newest version (or a specific train like 7.4
), and returns a fully qualified URL. If you specify a download path, it retrieves the MSI and computes its SHA256 hash for integrity verification.
Key Parameters
- Architecture:
amd64
(default) ori386
- Train: Optional (e.g.,
7.4
,7.2
). Auto-detects latest if omitted. - Channel: Defaults to
stable
- DownloadTo: Path to save the MSI (optional)
- ListAllVersions: Returns all patch versions for a train
- TimeoutSec, MaxRetries, RetryDelaySeconds: Network resilience options
Example Usage
# A) Get the newest overall Agent 2 (detect newest train automatically, amd64)
$r = Get-ZabbixAgent2LatestCdn -Architecture amd64 -Verbose
$r | Format-List
# B) Pin to a train (e.g., 7.2 LTS)
$rLts = Get-ZabbixAgent2LatestCdn -Train '7.2' -Architecture amd64
$rLts.Url
# C) Download and compute SHA256 with retry logic
$rDl = Get-ZabbixAgent2LatestCdn -Architecture amd64 -DownloadTo $env:TEMP -MaxRetries 5 -Verbose
$rDl.FilePath
$rDl.Sha256
# D) Download with explicit error handling
try {
$result = Get-ZabbixAgent2LatestCdn -Train '7.4' -DownloadTo 'C:\Temp\Zabbix' -Verbose
Write-Host "Successfully downloaded to: $($result.FilePath)" -ForegroundColor Green
Write-Host "SHA256: $($result.Sha256)" -ForegroundColor Green
}
catch {
Write-Error "Failed to download Zabbix Agent 2: $_"
}
The output includes:
- Full version (e.g.,
7.4.1
) - Architecture
- CDN URL
- SHA256 hash
- File size and path (if downloaded)
Why We Built This
As an MSP serving businesses in Boca Raton and throughout South Florida, we manage diverse IT environments where uptime and monitoring accuracy are critical. Automating repetitive tasks like agent updates allows us to focus on strategic IT initiatives for our clients—improving security, scalability, and performance.
Want to Simplify IT Management?
If your business struggles with manual updates, monitoring gaps, or IT inefficiencies, our team can help. We specialize in managed IT services, automation, and proactive monitoring solutions tailored for businesses in Boca Raton and the South Florida region.
👉 Contact us today to learn how we can streamline your IT operations.
The Code
function Get-ZabbixAgent2LatestCdn {
<#
.SYNOPSIS
Returns the fully versioned CDN URL for the latest Windows Zabbix Agent 2 MSI.
.PARAMETER Architecture
'amd64' (default) or 'i386'.
.PARAMETER Train
Optional (e.g., '7.4', '7.2', '7.0'). If omitted, the newest train is auto-detected from the CDN.
.PARAMETER Channel
CDN channel. Default: 'stable'.
.PARAMETER DownloadTo
Optional path to save the MSI. If provided, the file is downloaded and SHA256 is computed.
.PARAMETER MaxRetries
Maximum number of retry attempts for network operations. Default: 3.
.PARAMETER RetryDelaySeconds
Delay in seconds between retry attempts. Default: 2.
.PARAMETER TimeoutSec
Timeout in seconds for web requests. Default: 30.
.PARAMETER ListAllVersions
If specified, returns all available patch versions (X.Y.Z) for the selected train instead of the latest.
.PARAMETER BaseUrl
Base URL for Zabbix binaries CDN. Default: "https://cdn.zabbix.com/zabbix/binaries/".
.OUTPUTS
PSCustomObject with properties:
- Train: The major/minor train (e.g., '7.4').
- Version: The full version (e.g., '7.4.1').
- Architecture: The architecture of the Zabbix Agent 2 ('amd64' or 'i386').
- Url: The fully qualified URL to the MSI file.
- Sha256: The SHA256 hash of the downloaded MSI (if downloaded).
- FilePath: The file path provided or resolved for the MSI (may be relative or as given).
- FullFilePath: The absolute/resolved full file path to the MSI (if downloaded or exists).
- FileSize: The size of the MSI file in bytes (if available).
- Channel: The CDN channel used (e.g., 'stable').
- Source: The source domain (e.g., 'cdn.zabbix.com').
- FileName: The MSI file name.
.EXAMPLE
$r = Get-ZabbixAgent2LatestCdn -Architecture amd64
$r.Url
.EXAMPLE
$r = Get-ZabbixAgent2LatestCdn -Train '7.2' -DownloadTo 'C:\Temp' -Verbose
#>
[CmdletBinding()]
[OutputType([PSCustomObject], [String[]])] # Clarify output types
param(
[ValidateSet('amd64','i386')]
[string]$Architecture = 'amd64',
[ValidatePattern('^\d+\.\d+$')]
[string]$Train,
[ValidateSet('stable','lts','beta','alpha')]
[string]$Channel = 'stable',
[string]$DownloadTo,
[ValidateRange(1, 10)]
[int]$MaxRetries = 3,
[ValidateRange(1, 60)]
[int]$RetryDelaySeconds = 2,
[ValidateRange(10, 300)]
[int]$TimeoutSec = 30,
[switch]$ListAllVersions,
[string]$BaseUrl = "https://cdn.zabbix.com/zabbix/binaries/"
)
begin {
# Ensure TLS 1.2+ is enabled
try {
$currentProtocol = [Net.ServicePointManager]::SecurityProtocol
if ($currentProtocol -notmatch 'Tls12|Tls13') {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13
$PSCmdlet.WriteVerbose("Enabled TLS 1.2/1.3 for secure connections")
}
}
catch {
$PSCmdlet.WriteWarning("Failed to configure TLS protocol: $_")
}
# Join base URL and channel for web URLs
if ($BaseUrl.EndsWith('/')) {
$base = "$BaseUrl$Channel/"
} else {
$base = "$BaseUrl/$Channel/"
}
# Helper function for retrying web requests
function Invoke-WebRequestWithRetry {
param(
[string]$Uri,
[string]$Method = 'GET',
[string]$OutFile,
[int]$Retries = $MaxRetries,
[int]$Delay = $RetryDelaySeconds,
[int]$TimeoutSec = $TimeoutSec
)
$attempt = 0
$lastError = $null
while ($attempt -lt $Retries) {
$attempt++
try {
Write-Verbose "Attempt $attempt of $Retries for $Uri"
$params = @{
Uri = $Uri
UseBasicParsing = $true
ErrorAction = 'Stop'
TimeoutSec = $TimeoutSec
Headers = @{ 'User-Agent' = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) PowerShell Script' }
}
if ($Method) { $params['Method'] = $Method }
if ($OutFile) {
$params['OutFile'] = $OutFile
# Show progress for downloads
Write-Progress -Activity "Downloading Zabbix Agent 2" -Status "Downloading from CDN..." -PercentComplete 0
}
$response = Invoke-WebRequest @params
if ($OutFile) {
Write-Progress -Activity "Downloading Zabbix Agent 2" -Completed
}
return $response
}
catch {
$lastError = $_
Write-Verbose "Attempt $attempt failed: $($_.Exception.Message)"
if ($attempt -lt $Retries) {
Write-Verbose "Retrying in $Delay seconds..."
Start-Sleep -Seconds $Delay
}
}
}
# All retries exhausted
throw "Failed after $Retries attempts for $Uri. Last error: $($lastError.Exception.Message)"
}
}
process {
try {
# Auto-detect newest train if not provided
if (-not $Train) {
$PSCmdlet.WriteVerbose("Auto-detecting latest train from $base")
$root = Invoke-WebRequestWithRetry -Uri $base
if ([string]::IsNullOrWhiteSpace($root.Content)) {
throw "Empty response received from CDN root: $base"
}
$trains = [regex]::Matches($root.Content, 'href="(?<t>\d+\.\d+)/"') |
ForEach-Object { $_.Groups['t'].Value } |
Where-Object { $_ -match '^\d+\.\d+$' } |
Sort-Object { [version]$_ } -Descending
if (-not $trains -or $trains.Count -eq 0) {
throw "Could not enumerate trains at $base. Please specify -Train parameter explicitly."
}
$Train = $trains[0]
$PSCmdlet.WriteVerbose("Auto-detected train: $Train")
}
else {
$PSCmdlet.WriteVerbose("Using specified train: $Train")
}
# Enumerate patch versions under the chosen train
$trainUri = "$base$Train/"
$PSCmdlet.WriteVerbose("Fetching patch versions from $trainUri")
$trainPage = Invoke-WebRequestWithRetry -Uri $trainUri
if ([string]::IsNullOrWhiteSpace($trainPage.Content)) {
throw "Empty response received from train URI: $trainUri"
}
$versions = [regex]::Matches($trainPage.Content, 'href="(?<v>\d+\.\d+\.\d+)/"') |
ForEach-Object { $_.Groups['v'].Value } |
Where-Object { $_ -match '^\d+\.\d+\.\d+$' } |
Sort-Object { [version]$_ } -Descending
if (-not $versions -or $versions.Count -eq 0) {
throw "No patch directories (X.Y.Z) found under $trainUri. The train may not exist or may not have releases yet."
}
if ($ListAllVersions) {
$PSCmdlet.WriteVerbose("Returning all available patch versions for train $Train")
return $versions
}
$latestVersion = $versions[0]
$PSCmdlet.WriteVerbose("Latest version in train $Train is $latestVersion")
# Build the MSI URL
$file = "zabbix_agent2-$latestVersion-windows-$Architecture-openssl.msi"
$url = "$trainUri$latestVersion/$file"
$PSCmdlet.WriteVerbose("Constructed MSI URL: $url")
# HEAD request to verify MSI exists
$PSCmdlet.WriteVerbose("Verifying MSI availability...")
try {
$head = Invoke-WebRequestWithRetry -Uri $url -Method Head
if ($head.StatusCode -ne 200) {
throw "MSI returned unexpected status code: $($head.StatusCode)"
}
# Get file size if available
$fileSizeRaw = $head.Headers['Content-Length']
$fileSize = $null
if ($fileSizeRaw) {
# Ensure we get a single value and parse as long
$fileSizeStr = ($fileSizeRaw | Select-Object -First 1)
if ($fileSizeStr -match '^\d+$') {
$fileSize = [long]$fileSizeStr
$PSCmdlet.WriteVerbose("MSI file size: {0} MB" -f ([math]::Round($fileSize / 1MB, 2)))
} else {
$PSCmdlet.WriteVerbose("Content-Length header is not a valid number: $fileSizeStr")
}
}
}
catch {
throw "Expected MSI not found at $url. Error: $($_.Exception.Message)"
}
# Prepare result object
$result = [pscustomobject]@{
Train = $Train
Version = $latestVersion
Architecture = $Architecture
Url = $url
Sha256 = $null
FilePath = $null
FullFilePath = $null
FileSize = $fileSize
Channel = $Channel
Source = 'cdn.zabbix.com'
FileName = $file
}
# Optional download and hash computation
if ($DownloadTo) {
# Validate DownloadTo path
if ([string]::IsNullOrWhiteSpace($DownloadTo)) {
throw "DownloadTo path cannot be empty."
}
$PSCmdlet.WriteVerbose("Download requested to: $DownloadTo")
# Determine full destination path
$dest = $DownloadTo
if (Test-Path -LiteralPath $dest -PathType Container) {
$dest = Join-Path $dest $file
}
else {
$parent = Split-Path -Parent $dest
if ($parent -and -not (Test-Path $parent)) {
$PSCmdlet.WriteVerbose("Creating directory: $parent")
New-Item -ItemType Directory -Path $parent -Force -ErrorAction Stop | Out-Null
}
}
# Check if file already exists
if (Test-Path -LiteralPath $dest) {
$PSCmdlet.WriteVerbose("File already exists at destination: $dest")
$PSCmdlet.WriteVerbose("Computing hash of existing file...")
try {
$existingHash = (Get-FileHash -Path $dest -Algorithm SHA256 -ErrorAction Stop).Hash
# Verify existing file size matches expected
$existingSize = (Get-Item -LiteralPath $dest).Length
if ($fileSize -and $existingSize -ne [long]$fileSize) {
$PSCmdlet.WriteWarning("Existing file size ($existingSize bytes) differs from CDN ($fileSize bytes). Re-downloading...")
Remove-Item -LiteralPath $dest -Force -ErrorAction Stop
}
else {
$PSCmdlet.WriteVerbose("Using existing file with SHA256: $existingHash")
$result.FilePath = $dest
$result.FullFilePath = (Resolve-Path -LiteralPath $dest).Path
$result.Sha256 = $existingHash
return $result
}
}
catch {
$PSCmdlet.WriteWarning("Could not verify existing file: $_. Re-downloading...")
Remove-Item -LiteralPath $dest -Force -ErrorAction SilentlyContinue
}
}
# Download the file
$PSCmdlet.WriteVerbose("Downloading MSI to: $dest")
try {
Invoke-WebRequestWithRetry -Uri $url -OutFile $dest
# Verify download succeeded and file exists
if (-not (Test-Path -LiteralPath $dest)) {
throw "Download completed but file not found at destination: $dest"
}
$downloadedSize = (Get-Item -LiteralPath $dest).Length
if ($downloadedSize -eq 0) {
throw "Downloaded file is empty (0 bytes)"
}
$PSCmdlet.WriteVerbose("Download complete. File size: $([math]::Round($downloadedSize / 1MB, 2)) MB")
# Compute hash
$PSCmdlet.WriteVerbose("Computing SHA256 hash...")
$hash = Get-FileHash -Path $dest -Algorithm SHA256 -ErrorAction Stop
$result.FilePath = $dest
$result.FullFilePath = (Resolve-Path -LiteralPath $dest).Path
$result.Sha256 = $hash.Hash
$PSCmdlet.WriteVerbose("SHA256: $($hash.Hash)")
}
catch {
# Clean up partial download
if (Test-Path -LiteralPath $dest) {
$PSCmdlet.WriteWarning("Cleaning up partial download...")
Remove-Item -LiteralPath $dest -Force -ErrorAction SilentlyContinue
}
throw "Download failed: $($_.Exception.Message)"
}
}
return $result
}
catch {
$PSCmdlet.ThrowTerminatingError($_)
}
}
}
Let’s talk.
Book a free consultation with us today and discover how the right IT partner can transform your operations and future-proof your business.
Reach out to us today to learn how we can help optimize your IT infrastructure and ensure your business runs smoothly. 561-556-2000
Are you interested in more articles? Check out Syncro PowerShell to set Windows Power Settings