Deploy Prey on Windows Using a PowerShell Script

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

  1. Sign in to your Prey account.
  2. Click Add Device.
  3. 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 Administrator or SYSTEM privileges.
  • Configure the $FALLBACK_API_KEY variable 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 0

Need Help?

If you experience any issues during deployment, please contact our Support Team through the Support widget in your Prey Panel.

Was this article helpful?

0 out of 0 found this helpful