In this article
If you manage devices through an RMM or MDM solution, you can use the PowerShell script below to automatically install, update, or reinstall the latest version of the Prey Windows client.
What the Script Does
The script performs the following actions automatically:
- Removes existing Prey tasks.
- Uninstalls the current Prey installation (if exists).
- Downloads the latest Windows installer directly from Prey.
- Reinstalls and reconfigures the device.
- Verifies that the installation completed successfully.
Before You Begin
Find Your Setup Key
- Sign in to your Prey account.
- Click Add Device.
- Scroll down and copy the Setup Key displayed there
Configure the Script
Before deployment, edit the following variable near the beginning of the script:
$FALLBACK_API_KEY = ""
Replace the empty value with your Setup Key:
$FALLBACK_API_KEY = "YOUR_SETUP_KEY"Best Practices
- Run the script with
AdministratororSYSTEMprivileges. - Configure the
$FALLBACK_API_KEYvariable before deployment. - Test on a small number of devices before deploying organization-wide.
Installation Logs
The script generates logs that can be used for troubleshooting:
-
Reinstallation Log:
C:\Windows\Prey\reinstall.log
Please see the script below:
<#
.SYNOPSIS
Reinstalls or updates Prey Anti-Theft software.
.DESCRIPTION
This script handles the reinstallation or update process for Prey Anti-Theft software.
It performs the following operations:
- Removes existing Prey tasks
- Uninstalls the current Prey installation (if exists)
- Downloads the latest Prey version
- Installs the new version with appropriate configuration
- Verifies the installation was successful
The script supports configurable behavior through variables defined at the beginning:
- FALLBACK_API_KEY: API key to use if none is found on the system or if FORCE_API_KEY is true
- FORCE_API_KEY: Forces the use of FALLBACK_API_KEY regardless of existing configuration
- FORCE_REINSTALL: Controls whether to reinstall even if the current version is up to date
.NOTES
This script must be run with administrator privileges.
#>
#-----------------------------------------------------------------------------
# Configuration Variables
#-----------------------------------------------------------------------------
# FALLBACK_API_KEY: API key to use for installation when no existing key is found or when FORCE_API_KEY is true
# Mandatory: This must be set or the script will fail
# Type: String
$FALLBACK_API_KEY = ""
# FORCE_API_KEY: When true, always use the FALLBACK_API_KEY instead of any existing API key
# When false, attempt to retrieve the existing API key from the current installation
# Type: Boolean
$FORCE_API_KEY = $false
# FORCE_REINSTALL: When true, reinstall even if the current version matches the latest version
# When false, only reinstall if a newer version is available
# Type: Boolean
$FORCE_REINSTALL = $false
#-----------------------------------------------------------------------------
# Script Variables
#-----------------------------------------------------------------------------
$logFile = "C:\Windows\Temp\preyscript\reinstall.log"
#-----------------------------------------------------------------------------
# Validation and Initialization
#-----------------------------------------------------------------------------
# Check for administrator privileges
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Host "This script must be run as Administrator. Exiting." -ForegroundColor Red
exit 1
}
# Validate FALLBACK_API_KEY is set
if ([string]::IsNullOrEmpty($FALLBACK_API_KEY)) {
Write-Host "FALLBACK_API_KEY must be set in the script configuration. Exiting." -ForegroundColor Red
exit 1
}
# Create log directory if it doesn't exist
if (-not (Test-Path "C:\Windows\Temp\preyscript")) {
New-Item -Path "C:\Windows\Temp\preyscript" -ItemType Directory -Force | Out-Null
}
# Initialize log file
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Set-Content -Path $logFile -Value "[$timestamp] Starting Prey reinstallation process"
# Function for consistent logging
function Log-Message {
param([string]$message, [string]$level = "INFO")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$level] $message"
# Output to console with color based on level
switch ($level) {
"ERROR" { Write-Host $logEntry -ForegroundColor Red }
"WARNING" { Write-Host $logEntry -ForegroundColor Yellow }
default { Write-Host $logEntry }
}
# Append to log file
Add-Content -Path $logFile -Value $logEntry
}
# Log configuration settings
Log-Message "Script Configuration:"
Log-Message " FORCE_API_KEY: $FORCE_API_KEY"
Log-Message " FORCE_REINSTALL: $FORCE_REINSTALL"
#-----------------------------------------------------------------------------
# Remove Fenix Task
#-----------------------------------------------------------------------------
# Remove fenix task if exists
Log-Message "Attempting to remove Prey Fenix scheduled task..."
try {
$taskExists = Get-ScheduledTask -TaskName "Prey Fenix" -ErrorAction Stop
if ($taskExists) {
Unregister-ScheduledTask -TaskName "Prey Fenix" -Confirm:$false
Log-Message "Successfully removed Prey Fenix scheduled task."
} else {
Log-Message "Prey Fenix task not found. Continuing." "WARNING"
}
} catch {
Log-Message "Error removing Prey Fenix task: $($_.Exception.Message). Continuing." "WARNING"
}
#-----------------------------------------------------------------------------
# Retrieve Device Key and API Key
#-----------------------------------------------------------------------------
# Try to get current device key
Log-Message "Retrieving device key from current installation..."
$device_key = $null
$device_key_valid = $null
try {
if (Test-Path "C:\Windows\Prey\current\bin\prey") {
$device_key = cmd /c "C:\Windows\Prey\current\bin\prey config settings read control-panel.device_key" 2>$null
if (-not [string]::IsNullOrEmpty($device_key) -and $device_key -match "^[a-fA-F0-9]{1,6}$") {
$device_key_valid = $true
Log-Message "Device key successfully retrieved. this one: $($device_key)"
Log-Message "Device key is Valid: $($device_key_valid)"
} else {
Log-Message "Device key not found or empty. this one: $($device_key)" "WARNING"
}
} else {
Log-Message "Prey binary not found. No device key to retrieve." "WARNING"
}
} catch {
Log-Message "Error retrieving device key: $($_.Exception.Message)" "WARNING"
}
# Try to get current API key
$current_api_key = $null
try {
if (Test-Path "C:\Windows\Prey\current\bin\prey") {
$current_api_key = cmd /c "C:\Windows\Prey\current\bin\prey config settings read control-panel.api_key" 2>$null
if (-not [string]::IsNullOrEmpty($current_api_key) -and $current_api_key -match "^[a-zA-Z0-9]+$") {
Log-Message "API key successfully retrieved: $($current_api_key)."
} else {
Log-Message "API key not found or empty." "WARNING"
}
} else {
Log-Message "Prey binary not found. No API key to retrieve." "WARNING"
}
} catch {
Log-Message "Error retrieving API key: $($_.Exception.Message)" "WARNING"
}
# Determine which API key to use
$use_device_key = $false
if ($FORCE_API_KEY) {
$api_key_to_use = $FALLBACK_API_KEY
Log-Message "FORCE_API_KEY is enabled. Using FALLBACK_API_KEY."
} else {
if (-not [string]::IsNullOrEmpty($current_api_key) -and $current_api_key -match "^[a-zA-Z0-9]+$") {
$api_key_to_use = $current_api_key
Log-Message "Using API key from current installation."
} else {
$current_api_key = $FALLBACK_API_KEY
$api_key_to_use = $current_api_key
Log-Message "No API key found in current installation. Using FALLBACK_API_KEY."
}
}
# Determine if we should use the device key
if ($device_key_valid) {
if ($FORCE_API_KEY) {
# When FORCE_API_KEY is true, only use device key if API keys match
if (-not [string]::IsNullOrEmpty($current_api_key) -and $current_api_key -match "^[a-zA-Z0-9]+$" -and $current_api_key -eq $FALLBACK_API_KEY) {
$use_device_key = $true
Log-Message "API keys match. Will install with existing device key."
} else {
Log-Message "API keys don't match or couldn't be compared. Will install with API key only." "WARNING"
}
} else {
# When FORCE_API_KEY is false, use device key if it exists
$use_device_key = $true
Log-Message "Will install with existing device key."
}
}
#-----------------------------------------------------------------------------
# Get Latest Version
#-----------------------------------------------------------------------------
Log-Message "Fetching latest version information..."
try {
$latest_version = (Invoke-WebRequest -Uri "https://downloads.preyproject.com/prey-client-releases/node-client/latest.txt" -UseBasicParsing).Content.Trim()
Log-Message "Latest available version: $latest_version"
} catch {
Log-Message "Failed to fetch latest version: $($_.Exception.Message)" "ERROR"
exit 1
}
#-----------------------------------------------------------------------------
# Verify Credentials
#-----------------------------------------------------------------------------
# Verify credentials if not forced
$uri = $null
if ($FORCE_API_KEY -eq $false -and $use_device_key) {
$uri = "https://solid.preyproject.com/api/v2/devices/$($device_key)/verify.json"
} elseif ($FORCE_API_KEY -eq $false -and $use_device_key -eq $false) {
$uri = "https://solid.preyproject.com/api/v2/profile.json?lang=en"
} else {
Log-Message "No valid condition met for setting the URL." "WARNING"
}
if ($uri) {
try {
$headers = @{"User-Agent" = "Prey/$($latest_version) (Node v20.16.0 Windows 10.0.26100)"}
$base64Auth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($current_api_key):x"))
$headers["Authorization"] = "Basic $base64Auth"
$response = Invoke-WebRequest -Uri $uri -Headers $headers -Method Get -TimeoutSec 120 -UseBasicParsing
$responseData = $response.Content | ConvertFrom-Json
if ($responseData.result -eq "OK") {
$api_key_to_use = $current_api_key
Log-Message "Succesfuly verified credentials. Using API key: $($api_key_to_use) and Device Key: $($device_key) from current installation."
} elseif ($responseData.key -eq $current_api_key) {
$api_key_to_use = $current_api_key
Log-Message "The API KEY found in current installation is VALID. Using current_api_key: $($current_api_key)."
} else {
$api_key_to_use = $FALLBACK_API_KEY
$use_device_key = $false
Log-Message "The credentials found in current installation are NOT VALID. Using FALLBACK_API_KEY: $($FALLBACK_API_KEY)."
}
} catch {
$api_key_to_use = $FALLBACK_API_KEY
$use_device_key = $false
Log-Message "Error verifying credentials [Api Key: $($api_key_to_use), Device Key: $($device_key)]: $($_.Exception.Message)" "WARNING"
}
} else {
Log-Message "Skipping credential verification due to uri configuration." "WARNING"
}
#-----------------------------------------------------------------------------
# Get Current Version
#-----------------------------------------------------------------------------
$current_version = $null
try {
if (Test-Path "C:\Windows\Prey\current\bin\prey") {
$current_version = cmd /c "C:\Windows\Prey\current\bin\prey --version" 2>$null
$current_version = $current_version.Trim()
Log-Message "Current Prey version: $current_version"
} else {
Log-Message "No current installation found or prey binary missing."
}
} catch {
Log-Message "Error reading current version: $($_.Exception.Message)" "WARNING"
}
# Check if reinstallation is needed based on FORCE_REINSTALL setting
if (-not $FORCE_REINSTALL -and -not [string]::IsNullOrEmpty($current_version)) {
# Compare versions to determine if update is needed
if ($current_version -eq $latest_version) {
Log-Message "Current version ($current_version) is already the latest. FORCE_REINSTALL is disabled, so exiting."
exit 0
} elseif ([Version]$current_version -ge [Version]$latest_version) {
# This handles cases where current version might be newer than what's reported as latest
Log-Message "Current version ($current_version) is newer than or equal to latest version ($latest_version). FORCE_REINSTALL is disabled, so exiting."
exit 0
} else {
Log-Message "Current version ($current_version) is older than latest version ($latest_version). Proceeding with update."
}
}
#-----------------------------------------------------------------------------
# Uninstall Existing Prey Installation
#-----------------------------------------------------------------------------
# Attempt to uninstall Prey Anti-Theft
Log-Message "Attempting to uninstall current Prey installation..."
$uninstalled = $false
try {
Log-Message "Attempting WMI-based uninstallation..."
$preyApp = Get-WmiObject Win32_Product -Filter "Name='Prey Anti-Theft'" -ErrorAction Stop
if ($preyApp) {
$preyApp.Uninstall() | Out-Null
$uninstalled = $true
Log-Message "Uninstallation completed via WMI."
}
} catch {
Log-Message "WMI uninstallation failed: $($_.Exception.Message)" "WARNING"
}
# If all else fails, force kill processes and remove services
if (-not $uninstalled) {
Log-Message "Using fallback uninstallation method." "WARNING"
# Check and stop services
try {
$cronService = Get-Service -Name "CronService" -ErrorAction Stop
if ($cronService) {
try {
Set-Service -Name "CronService" -StartupType Disabled
Stop-Service -Name "CronService" -Force -ErrorAction SilentlyContinue
Log-Message "CronService disabled and stopped."
} catch {
Log-Message "Error stopping CronService: $($_.Exception.Message)" "WARNING"
}
}
} catch {
Log-Message "CronService not found: $($_.Exception.Message)" "WARNING"
}
# Check and kill processes
$processes = @("wpxsvc", "node")
foreach ($process in $processes) {
try {
$runningProcesses = Get-Process -Name $process -ErrorAction Stop
if ($runningProcesses) {
Stop-Process -Name $process -Force
Log-Message "Killed process: $process"
}
} catch {
Log-Message "Error killing process $($process): $($_.Exception.Message)" "WARNING"
}
}
# Try to delete the service
try {
$service = Get-WmiObject -Class Win32_Service -Filter "Name='CronService'" -ErrorAction Stop
if ($service) {
$service.Delete() | Out-Null
Log-Message "CronService deleted."
}
} catch {
Log-Message "Error deleting CronService: $($_.Exception.Message)" "WARNING"
}
}
#-----------------------------------------------------------------------------
# Clean Up Prey Folder
#-----------------------------------------------------------------------------
# Clean up Prey folder
Log-Message "Removing Prey folder..."
$prey_folder = "C:\Windows\Prey"
if (Test-Path $prey_folder) {
try {
# First try to remove contents to handle permissions better
try {
Get-ChildItem -Path $prey_folder -Recurse | Remove-Item -Force -Recurse -ErrorAction Stop
} catch {
Log-Message "Error removing Prey folder contents: $($_.Exception.Message)" "WARNING"
}
try {
Remove-Item -Path $prey_folder -Recurse -Force -ErrorAction Stop
} catch {
Log-Message "Error removing Prey folder: $($_.Exception.Message)" "WARNING"
}
# Verify removal
if (Test-Path $prey_folder) {
Log-Message "Warning: Could not completely remove $prey_folder" "WARNING"
} else {
Log-Message "Successfully removed Prey folder."
}
} catch {
Log-Message "Error removing Prey folder: $($_.Exception.Message)" "WARNING"
}
}
#-----------------------------------------------------------------------------
# Download and Install Latest Version
#-----------------------------------------------------------------------------
# Determine architecture
if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64") {
$arch_version = "x64"
} else {
$arch_version = "x86"
}
# Set download and output paths
$msi_filename = "prey-windows-$latest_version-$arch_version.msi"
$download_url = "https://downloads.preyproject.com/prey-client-releases/node-client/$latest_version/$msi_filename"
$output_filepath = Join-Path "C:\Windows\Temp" $msi_filename
# Download the installer
Log-Message "Downloading Prey $latest_version for $arch_version architecture from $download_url"
try {
Invoke-WebRequest -Uri $download_url -OutFile $output_filepath -UseBasicParsing
# Verify download success
if (-not (Test-Path $output_filepath) -or (Get-Item $output_filepath).Length -eq 0) {
Log-Message "Failed to download Prey installer or file is empty." "ERROR"
exit 1
}
Log-Message "Successfully downloaded installer to $output_filepath"
} catch {
Log-Message "Failed to download Prey installer: $($_.Exception.Message)" "ERROR"
exit 1
}
# Install Prey based on configuration
Log-Message "Installing Prey..."
try {
$installArgs = ""
if ($use_device_key) {
Log-Message "Installing with existing device key and API key: $api_key_to_use"
$installArgs = "/i `"$output_filepath`" /q /lv C:\Windows\Temp\preyscript\installer.log AGREETOLICENSE=yes DEVICE_KEY=`"$device_key`" API_KEY=`"$api_key_to_use`""
} else {
Log-Message "Installing with API key only: $api_key_to_use"
$installArgs = "/i `"$output_filepath`" /q /lv C:\Windows\Temp\preyscript\installer.log AGREETOLICENSE=yes API_KEY=`"$api_key_to_use`""
}
$process = Start-Process msiexec.exe -ArgumentList $installArgs -Wait -PassThru
$exitCode = $process.ExitCode
if ($exitCode -eq 0) {
Log-Message "Installation completed successfully with exit code: $exitCode"
} else {
Log-Message "Installation completed with non-zero exit code: $exitCode" "WARNING"
}
} catch {
Log-Message "Error during installation: $($_.Exception.Message)" "ERROR"
exit 1
}
#-----------------------------------------------------------------------------
# Verify Installation
#-----------------------------------------------------------------------------
# Verify installation
Log-Message "Verifying installation..."
if (Test-Path "C:\Windows\Prey\current\bin\prey") {
Log-Message "Prey binary found. Installation appears successful."
# Try to get installed version for verification
try {
$installed_version = cmd /c "C:\Windows\Prey\current\bin\prey --version" 2>$null
$installed_version = $installed_version.Trim()
Log-Message "Installed Prey version: $installed_version"
if ($installed_version -ne $latest_version) {
Log-Message "Warning: Installed version ($installed_version) does not match expected version ($latest_version)" "WARNING"
}
} catch {
Log-Message "Could not verify installed version: $($_.Exception.Message)" "WARNING"
}
# Check if CronService is running
try {
$cronService = Get-Service -Name "CronService" -ErrorAction Stop
Log-Message "CronService status: $($cronService.Status)"
if ($cronService.Status -ne "Running") {
Log-Message "Warning: CronService is not running" "WARNING"
} else {
Log-Message "CronService is running properly."
}
} catch {
Log-Message "Error checking CronService: $($_.Exception.Message)" "ERROR"
}
# Check if wpxsvc.exe is running
try {
$wpxsvcProcess = Get-Process -Name "wpxsvc" -ErrorAction Stop
Log-Message "wpxsvc.exe is running (PID: $($wpxsvcProcess.Id))"
} catch {
Log-Message "wpxsvc.exe is not running: $($_.Exception.Message)" "WARNING"
}
# Log wpxsvc version
try {
if (Test-Path "C:\Windows\Prey\wpxsvc.exe") {
$wpxsvcVersion = cmd /c "C:\Windows\Prey\wpxsvc.exe -winsvc=version" 2>$null
$wpxsvcVersion = $wpxsvcVersion.Trim()
Log-Message "wpxsvc.exe version: $wpxsvcVersion"
} else {
Log-Message "wpxsvc.exe not found" "WARNING"
}
} catch {
Log-Message "Error getting wpxsvc version: $($_.Exception.Message)" "WARNING"
}
} else {
Log-Message "Prey binary not found. Installation may have failed." "ERROR"
# Check installation log for errors
if (Test-Path "C:\Windows\Temp\preyscript\installer.log") {
$logContent = Get-Content "C:\Windows\Temp\preyscript\installer.log"
$errorLines = $logContent | Select-String -Pattern "Error|failed|failure" -CaseSensitive:$false
if ($errorLines) {
Log-Message "Found potential errors in installation log:" "ERROR"
foreach ($line in $errorLines) {
Log-Message " $line" "ERROR"
}
}
} else {
Log-Message "Installation log not found." "ERROR"
}
exit 1
}
#-----------------------------------------------------------------------------
# Clean Up and Finalize
#-----------------------------------------------------------------------------
# Clean up downloaded installer
try {
if (Test-Path $output_filepath) {
Remove-Item -Path $output_filepath -Force
Log-Message "Cleaned up downloaded installer."
}
} catch {
Log-Message "Error cleaning up installer file: $($_.Exception.Message)" "WARNING"
}
# Move log files to Prey folder
try {
# Create Prey folder if it doesn't exist (should exist after installation)
if (-not (Test-Path "C:\Windows\Prey")) {
New-Item -Path "C:\Windows\Prey" -ItemType Directory -Force | Out-Null
}
# Move and rename installer.log
if (Test-Path "C:\Windows\Temp\preyscript\installer.log") {
Copy-Item -Path "C:\Windows\Temp\preyscript\installer.log" -Destination "C:\Windows\Prey\msi_install.log" -Force
Log-Message "Moved installer.log to C:\Windows\Prey\msi_install.log"
}
# Move reinstall.log
if (Test-Path $logFile) {
Copy-Item -Path $logFile -Destination "C:\Windows\Prey\reinstall.log" -Force
Log-Message "Moved reinstall.log to C:\Windows\Prey\reinstall.log"
}
} catch {
Log-Message "Error moving log files: $($_.Exception.Message)" "WARNING"
}
# Log final versions as a summary
Log-Message "Installation Summary:"
try {
$finalPreyVersion = cmd /c "C:\Windows\Prey\current\bin\prey --version" 2>$null
Log-Message " Prey Agent Version: $($finalPreyVersion.Trim())"
} catch {
Log-Message " Could not determine final Prey Agent version: $($_.Exception.Message)" "WARNING"
}
try {
if (Test-Path "C:\Windows\Prey\wpxsvc.exe") {
$finalWpxsvcVersion = cmd /c "C:\Windows\Prey\wpxsvc.exe -winsvc=version" 2>$null
Log-Message " Windows Service (wpxsvc.exe) Version: $($finalWpxsvcVersion.Trim())"
} else {
Log-Message " Windows Service (wpxsvc.exe) not found" "WARNING"
}
} catch {
Log-Message " Could not determine Windows Service version: $($_.Exception.Message)" "WARNING"
}
Log-Message "Script completed successfully."
exit 0Need Help?
If you experience any issues during deployment, please contact our Support Team through the Support widget in your Prey Panel.