Nightfall Windows Agent: MSI Deployment
This guide explains multiple ways to deploy the Nightfall Agent (NightfallAgent.msi) with the required API_KEY and COMPANY_ID parameters.
We cover:
PowerShell scripts (local, network share, download from URL)
Assumptions
You have the MSI installer (NightfallAgent.msi) provided by Nightfall.
Installation requires two properties:
API_KEY="YOUR-API-KEY"
COMPANY_ID="YOUR_SECRET_VALUE"
Installation is silent (/qn /norestart) and requires administrator rights.
Logging is enabled with /l*v for troubleshooting.
PowerShell: Local MSI (already copied to the machine)
Use this if you or your RMM tool place the .msi directly on the machine before running the script.
# Install-NightfallAgent-Local.ps1
$msiPath = "C:\Temp\NightfallAgent.msi"
$apiKey = "REPLACE_WITH_API_KEY"
$companyId = "REPLACE_WITH_COMPANY_ID"
$logDir = "C:\Windows\Temp\Nightfall"
$logFile = Join-Path $logDir "NightfallAgent_Install.log"
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
if (Test-Path $msiPath) {
Write-Output "MSI found at $msiPath. Starting install..."
$args = "/i `"$msiPath`" API_KEY=`"$apiKey`" COMPANY_ID=`"$companyId`" /qn /norestart /l*v `"$logFile`""
$proc = Start-Process "msiexec.exe" -ArgumentList $args -Wait -PassThru -NoNewWindow
if ($proc.ExitCode -eq 0) {
Write-Output "Nightfall agent installed successfully."
} else {
Write-Output "Installer returned exit code $($proc.ExitCode). Check log: $logFile"
exit $proc.ExitCode
}
} else {
Write-Output "MSI not found at $msiPath. Skipping install."
exit 2
}
PowerShell: Install from a Network Share
Use this if you keep the MSI on a file server. Make sure Domain Computers or the target machines have read access to the share.
⚠️ Use UNC paths (\\server\share\...) — mapped drives won’t work for GPO Startup scripts.
# Install-NightfallAgent-FromShare.ps1
$sourceMsi = "\\fileserver\software\Nightfall\NightfallAgent.msi"
$localMsi = "C:\Temp\NightfallAgent.msi"
$apiKey = "YOUR_API_KEY_HERE"
$companyId = "YOUR_SECRET_VALUE"
$logDir = "C:\Windows\Temp\Nightfall"
$logFile = Join-Path $logDir "NightfallAgent_Install.log"
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
New-Item -ItemType Directory -Path (Split-Path $localMsi) -Force | Out-Null
Write-Output "Copying MSI from $sourceMsi to $localMsi..."
Copy-Item -Path $sourceMsi -Destination $localMsi -Force -ErrorAction Stop
if (Test-Path $localMsi) {
Write-Output "Copy complete. Starting install..."
$args = "/i `"$localMsi`" API_KEY=`"$apiKey`" COMPANY_ID=`"$companyId`" /qn /norestart /l*v `"$logFile`""
$proc = Start-Process "msiexec.exe" -ArgumentList $args -Wait -PassThru -NoNewWindow
if ($proc.ExitCode -eq 0) {
Write-Output "Nightfall agent installed successfully."
} else {
Write-Output "Installer returned exit code $($proc.ExitCode). Check log: $logFile"
exit $proc.ExitCode
}
} else {
Write-Output "MSI copy failed. Check share permissions and path."
exit 3
}
PowerShell: Download MSI from a URL
Use this if you host the MSI on an internal HTTPS server or CDN.
# Install-NightfallAgent-FromUrl.ps1
# Purpose: Download the Nightfall MSI from a URL, validate it looks like a real MSI, then install silently.
# Notes:
# - Run elevated (admin). Works as a GPO Startup script.
# --- EDIT THESE VALUES ---
$downloadUrl = "https://example.com/NightfallAgent.msi" # <-- Replace with your direct MSI URL
$localMsi = "C:\Temp\NightfallAgent.msi"
$apiKey = "<API_KEY>" # <-- Replace
$companyId = "<COMPANY_ID>" # <-- Replace
# --------------------------
$ErrorActionPreference = "Stop"
# Paths for logging
$logDir = "C:\Windows\Temp\Nightfall"
$logFile = Join-Path $logDir "NightfallAgent_Install.log"
# Ensure folders exist
New-Item -ItemType Directory -Path (Split-Path $localMsi) -Force | Out-Null
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
# Helper: quick MSI signature + size sanity check
function Test-IsMsi {
param([string]$Path)
if (-not (Test-Path $Path)) { return $false }
$len = (Get-Item $Path).Length
if ($len -lt 1MB) { return $false } # tiny files are likely HTML/error pages
# MSI is a CFBF (OLE) container: header D0 CF 11 E0 A1 B1 1A E1
$fs = [System.IO.File]::Open($Path, 'Open', 'Read', 'ReadWrite')
try {
$buf = New-Object byte[] 8
[void]$fs.Read($buf, 0, 8)
$hex = ($buf | ForEach-Object { $_.ToString("X2") }) -join " "
return ($hex -eq "D0 CF 11 E0 A1 B1 1A E1")
} finally {
$fs.Close()
}
}
Write-Output "Downloading MSI from $downloadUrl ..."
try {
# Use HttpClient for robust redirects + streaming
Add-Type -AssemblyName System.Net.Http
$handler = New-Object System.Net.Http.HttpClientHandler
$handler.AllowAutoRedirect = $true
$handler.AutomaticDecompression = [System.Net.DecompressionMethods]::GZip -bor `
[System.Net.DecompressionMethods]::Deflate -bor `
[System.Net.DecompressionMethods]::Brotli
$client = New-Object System.Net.Http.HttpClient($handler)
$client.Timeout = [TimeSpan]::FromMinutes(10)
$client.DefaultRequestHeaders.UserAgent.ParseAdd("Nightfall-Agent-Installer/1.0")
$response = $client.GetAsync($downloadUrl, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).GetAwaiter().GetResult()
if (-not $response.IsSuccessStatusCode) {
throw "HTTP $([int]$response.StatusCode) $($response.ReasonPhrase)"
}
$stream = $response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
$tmp = "$localMsi.download"
$fs = [System.IO.File]::Open($tmp, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None)
try {
$buffer = New-Object byte[] (1024*256) # 256 KB chunks
while (($read = $stream.Read($buffer, 0, $buffer.Length)) -gt 0) {
$fs.Write($buffer, 0, $read)
}
} finally {
$fs.Dispose()
$stream.Dispose()
$client.Dispose()
$handler.Dispose()
}
if (Test-Path $localMsi) { Remove-Item $localMsi -Force }
Move-Item $tmp $localMsi -Force
} catch {
Write-Error "Download failed: $($_.Exception.Message)"
exit 100
}
# Validate the download looks like a real MSI
if (-not (Test-IsMsi -Path $localMsi)) {
$size = (Get-Item $localMsi).Length
Write-Error "Downloaded file does not look like a valid MSI (size=$size bytes). The URL may be a landing page or error."
exit 101
}
# Remove MOTW just in case
try { Unblock-File -Path $localMsi -ErrorAction SilentlyContinue } catch {}
# Install silently with logging
Write-Output "MSI validated. Installing Nightfall Agent..."
$args = "/i `"$localMsi`" API_KEY=`"$apiKey`" COMPANY_ID=`"$companyId`" /qn /norestart /l*v `"$logFile`""
$proc = Start-Process "msiexec.exe" -ArgumentList $args -Wait -PassThru -NoNewWindow
switch ($proc.ExitCode) {
0 { Write-Output "Nightfall Agent installed successfully."; exit 0 }
1603 { Write-Error "Fatal error during installation (1603). See log: $logFile"; exit 1603 }
1618 { Write-Error "Another installation is already in progress (1618)."; exit 1618 }
1620 { Write-Error "Package could not be opened (1620). File may be invalid. See log: $logFile"; exit 1620 }
default { Write-Error "Installer returned exit code $($proc.ExitCode). See log: $logFile"; exit $proc.ExitCode }
}
GPO Deployment via Startup Script
Recommended for domain-joined Windows machines. Use a Startup Script because the built-in “Software Installation” GPO cannot pass custom properties like API_KEY.
Steps:
Place the script (e.g., Install-NightfallAgent-FromShare.ps1) in
\\<domain>\SYSVOL\<domain>\scripts\Nightfall\
Ensure Domain Computers have read access.
In Group Policy Management:
Go to Computer Configuration → Policies → Windows Settings → Scripts (Startup/Shutdown).
Add a Startup Script.
Script name:
powershell.exe
Script parameters:
-ExecutionPolicy Bypass -File "\\SYSVOL<domain>\scripts\Nightfall\Install-NightfallAgent-FromShare.ps1"
Apply the GPO to the desired OU.
Run gpupdate /force or reboot a target machine.
GPO Software Installation with MST (Advanced)
If you have an MST transform that embeds API_KEY and COMPANY_ID, you can deploy the MSI via:
Computer Configuration → Policies → Software Settings → Software installation.
Add the MSI via UNC path.
Open its Properties → Modifications → Add your .mst.
Without an MST, use GPO via Startup Script instead. One-liner for Testing
One-liner for Testing
Run manually on a single machine (PowerShell elevated):
$msiPath="C:\Temp\NightfallAgent.msi"; Start-Process msiexec.exe -ArgumentList "/i `"$msiPath`" API_KEY=`"YOUR_API_KEY_HERE`" COMPANY_ID=`"YOUR_SECRET_VALUE`" /qn /norestart /l*v `"`"C:\Windows\Temp\Nightfall\NightfallAgent_Install.log`"`"" -Wait
Verification After Install

Check for expected services:
Get-Service Nightfall*
Confirm presence of the Nightfall AI icon in the system tray (this may take a few seconds).
Double click the icon
You should see a connected status as seen in the image above.
Uninstalling The Nightfall AI Agent
$ProductName = "NightfallAI Agent"
# Function to retrieve installed products matching product name
function Get-MatchingProducts($name) {
Write-Host "Searching for products matching: '$name'..."
Get-WmiObject -Class Win32_Product -ErrorAction SilentlyContinue |
Where-Object { $_.Name -like "*$name*" }
}
# Function to uninstall a product by ProductCode
function Uninstall-Product($product) {
$name = $product.Name
$productCode = $product.IdentifyingNumber
if ($productCode) {
Write-Host "Uninstalling '$name' (ProductCode: $productCode)..." -ForegroundColor Green
Start-Process "msiexec.exe" -ArgumentList "/x $productCode /qn" -Wait -NoNewWindow
Write-Host "Uninstalled: $name" -ForegroundColor Green
} else {
Write-Warning "Skipping ${name}: missing ProductCode."
}
}
# Try finding the initial product
$products = Get-MatchingProducts -name $ProductName
# If not found, try old NightfallAI Agent name 'Agent'
if (-not $products -or $products.Count -eq 0) {
Write-Warning "No installed products found matching: '$ProductName'"
Write-Host "Trying to search for old NightfallAgent name : 'Agent'" -ForegroundColor Yellow
$products = Get-MatchingProducts -name "Agent"
}
# Final check before uninstall
if (-not $products -or $products.Count -eq 0) {
Write-Host "No matching products found for either '${ProductName}' or 'Agent'."
exit 1
}
foreach ($product in $products) {
Uninstall-Product -product $product
}
Last updated
Was this helpful?