<# .SYNOPSIS Downloads and silently installs Zabbix Agent 2 for Windows, configured for a Zabbix Proxy/Server, and opens the firewall for passive checks. .DESCRIPTION Downloads the Zabbix Agent 2 Windows MSI from cdn.zabbix.com and installs it silently via msiexec. Hostname is set to the local computer name; both Server and ServerActive are set to the Zabbix Proxy/Server IP address supplied by the caller (interactively, via -ZabbixProxyIPAddress, or via $env:ZabbixProxyIPAddress). After a successful install it creates a Windows Firewall rule allowing inbound TCP 10050 (Zabbix Agent passive checks) from that same IP address only. Must be run elevated (Administrator). .PARAMETER ZabbixProxyIPAddress IP address of the Zabbix Proxy or Zabbix Server. Used for both the agent's Server and ServerActive settings, and as the only allowed remote address for the TCP 10050 firewall rule. Omit this parameter to be prompted interactively (or to fall back to $env:ZabbixProxyIPAddress if that is set). .PARAMETER ZabbixAgentMsiUri URI of the Zabbix Agent 2 Windows MSI to download and install. Defaults to version 7.2.13. Override this to install a different version/build, e.g. one of the other files listed under https://cdn.zabbix.com/zabbix/binaries/stable///. .EXAMPLE PS> .\Install-ZBXAgentManual.ps1 Downloads and installs Zabbix Agent 2, prompting for the Zabbix Proxy/Server IP address. .EXAMPLE PS> .\Install-ZBXAgentManual.ps1 -ZabbixProxyIPAddress '192.168.10.50' Downloads and installs Zabbix Agent 2 non-interactively. .EXAMPLE PS> .\Install-ZBXAgentManual.ps1 -ZabbixProxyIPAddress '192.168.10.50' -ZabbixAgentMsiUri 'https://cdn.zabbix.com/zabbix/binaries/stable/7.0/7.0.14/zabbix_agent2-7.0.14-windows-amd64-openssl.msi' Installs a different Zabbix Agent 2 version instead of the default 7.2.13. .EXAMPLE PS> irm https://s.tslab.cz/ZBXAgent | iex Shortest way to run this directly from the repo without saving it locally first; prompts interactively for the Zabbix Proxy/Server IP address and installs the default MSI version. .NOTES Author: Petr Stepan Created: 2026-07-21 Version: 1.1.0 Changelog: 1.0.0 - Initial version 1.1.0 - Made ZabbixAgentMsiUri a parameter (was hardcoded) so a different agent version can be installed without editing the script #> [CmdletBinding()] param( [Parameter(Mandatory = $false, HelpMessage = 'IP address of the Zabbix Proxy or Server. Used for Server, ServerActive and the TCP 10050 firewall rule.')] [string] $ZabbixProxyIPAddress, [Parameter(Mandatory = $false, HelpMessage = 'URI of the Zabbix Agent 2 Windows MSI to download and install.')] [ValidateNotNullOrEmpty()] [string] $ZabbixAgentMsiUri = 'https://cdn.zabbix.com/zabbix/binaries/stable/7.2/7.2.13/zabbix_agent2-7.2.13-windows-amd64-openssl.msi' ) # --- Safety preamble ------------------------------------------------------- Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # Older Windows Server releases don't enable TLS 1.2 by default, which would make the # download below fail silently against a TLS-1.2-only server. [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $MsiPath = Join-Path -Path $env:TEMP -ChildPath (Split-Path -Path $ZabbixAgentMsiUri -Leaf) $MsiLogPath = Join-Path -Path $env:TEMP -ChildPath 'zabbix_agent2_install.log' $FirewallRuleName = 'Zabbix Agent (TCP-In)' $FirewallPort = 10050 # --- Functions --------------------------------------------------------- function Test-CurrentUserIsAdministrator { <# .SYNOPSIS Returns whether the current process is running elevated (Administrator). #> [CmdletBinding()] param() $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object -TypeName System.Security.Principal.WindowsPrincipal -ArgumentList $identity return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } function Invoke-RemoteFileDownload { <# .SYNOPSIS Downloads a single file from a URI to a local path. .PARAMETER Uri Source URI to download from. .PARAMETER OutFile Local destination file path. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Uri, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $OutFile ) try { Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing Write-Host "Downloaded '$Uri' to '$OutFile'." } catch { Write-Error "Failed to download '$Uri' to '$OutFile': $_" throw } } function Install-ZabbixAgentMsi { <# .SYNOPSIS Silently installs the Zabbix Agent 2 MSI with the given Hostname/Server settings. .PARAMETER MsiPath Path to the downloaded zabbix_agent2 MSI file. .PARAMETER HostName Value for the agent's Hostname setting. .PARAMETER ServerAddress Value used for both the Server and ServerActive settings. .PARAMETER LogPath Path for the msiexec verbose install log. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] [string] $MsiPath, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $HostName, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ServerAddress, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $LogPath ) # Passed as an array, not one concatenated string, so Start-Process quotes each # argument correctly even if $MsiPath/$LogPath contain spaces. $msiArgs = @( '/i', $MsiPath '/qn' "HOSTNAME=$HostName" "SERVER=$ServerAddress" "SERVERACTIVE=$ServerAddress" '/l*v', $LogPath ) Write-Host "Installing Zabbix Agent 2 (Hostname=$HostName, Server/ServerActive=$ServerAddress)..." $process = Start-Process -FilePath 'msiexec.exe' -ArgumentList $msiArgs -Wait -PassThru -NoNewWindow # 3010 = success, reboot required - still a successful install. if ($process.ExitCode -notin @(0, 3010)) { throw "msiexec failed with exit code $($process.ExitCode). See log: $LogPath" } Write-Host "Zabbix Agent 2 installed successfully (msiexec exit code $($process.ExitCode))." } function New-ZabbixAgentFirewallRule { <# .SYNOPSIS Creates an inbound firewall rule for the Zabbix Agent listener, restricted to a single remote address. Does nothing if a rule with the same name already exists. .PARAMETER Name Display name of the firewall rule. .PARAMETER Port TCP port to allow. .PARAMETER RemoteAddress Only remote address allowed to reach that port. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter(Mandatory = $true)] [int] $Port, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $RemoteAddress ) if (Get-NetFirewallRule -DisplayName $Name -ErrorAction SilentlyContinue) { Write-Host "Firewall rule '$Name' already exists - skipping." return } New-NetFirewallRule -DisplayName $Name -Direction Inbound -Action Allow -Protocol TCP ` -LocalPort $Port -RemoteAddress $RemoteAddress -Group 'Zabbix Agent' ` -Description "Allow the Zabbix Proxy/Server ($RemoteAddress) to reach the Zabbix Agent passive check listener." | Out-Null Write-Host "Firewall rule '$Name' created (TCP $Port, remote address $RemoteAddress)." } # --- Main -------------------------------------------------------------- try { if (-not (Test-CurrentUserIsAdministrator)) { throw 'This script must be run elevated (as Administrator).' } # Only fall back to the environment variable / prompt when the caller did not pass # the parameter at all. $env:ZabbixProxyIPAddress lets a plain "irm | iex" # one-liner supply a value without needing "& { ... } -Param" - piping into iex # runs the downloaded param() block as a plain statement with no bound arguments, # so -ZabbixProxyIPAddress itself can only be set that way when the script is # invoked as a real command (locally or via "& { ... }"). if (-not $PSBoundParameters.ContainsKey('ZabbixProxyIPAddress')) { if ($env:ZabbixProxyIPAddress) { $ZabbixProxyIPAddress = $env:ZabbixProxyIPAddress } else { $ZabbixProxyIPAddress = Read-Host -Prompt 'Enter Zabbix Proxy/Server IP address' } } $parsedIp = $null if (-not [System.Net.IPAddress]::TryParse($ZabbixProxyIPAddress, [ref] $parsedIp)) { throw "'$ZabbixProxyIPAddress' is not a valid IP address." } Invoke-RemoteFileDownload -Uri $ZabbixAgentMsiUri -OutFile $MsiPath Install-ZabbixAgentMsi -MsiPath $MsiPath -HostName $env:COMPUTERNAME -ServerAddress $ZabbixProxyIPAddress -LogPath $MsiLogPath New-ZabbixAgentFirewallRule -Name $FirewallRuleName -Port $FirewallPort -RemoteAddress $ZabbixProxyIPAddress Remove-Item -Path $MsiPath -Force -ErrorAction SilentlyContinue } catch { Write-Error "Script failed: $_" # $PSCommandPath is only set when this runs as an actual .ps1 file. When run via # "irm | iex" there is no backing file - the code executes in the caller's # own session, so calling exit here would close their whole PowerShell window # instead of just ending this script. if ($PSCommandPath) { exit 1 } return } if ($PSCommandPath) { exit 0 }