# Nightfall Windows Agent Deployment: MSI

We cover:

* PowerShell scripts ([local](#powershell-local-msi-already-copied-to-the-machine), [network share](#powershell-install-from-a-network-share), [download from URL](#powershell-download-msi-from-a-url))
* [Deployment through Group Policy (GPO)](#gpo-deployment-via-startup-script)
* [One-liner script for testing](#one-liner-for-testing)
* [Uninstall with or without the .msi present.](#uninstalling-the-nightfall-ai-agent)

### 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.

```powershell
# 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.<br>

> ⚠️ Use UNC paths (\\\server\share\\...) — mapped drives won’t work for GPO Startup scripts.

```powershell
# 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.

```powershell
# 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:

1. Place the script (e.g., Install-NightfallAgent-FromShare.ps1) in

   \\\\\<domain>\SYSVOL\\\<domain>\scripts\Nightfall\\
2. Ensure Domain Computers have read access.
3. In Group Policy Management:
   * Go to Computer Configuration → Policies → Windows Settings → Scripts (Startup/Shutdown).
   * Add a Startup Script.
     * Script name: `powershell.exe`&#x20;
     * Script parameters: `-ExecutionPolicy Bypass -File "\\SYSVOL<domain>\scripts\Nightfall\Install-NightfallAgent-FromShare.ps1"`
4. Apply the GPO to the desired OU.
5. 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:&#x20;

**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

<figure><img src="/files/Tdh7EAtOcEs7kOumB3ub" alt="" width="269"><figcaption></figcaption></figure>

* Check for expected services:

{% code fullWidth="false" %}

```powershell
Get-Service Nightfall*
```

{% endcode %}

* 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

```powershell
$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
}

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://help.nightfall.ai/data-exfiltration-prevention/exfiltration_endpoint/install-nightfall-ai-agent-for-windows-os/nightfall-windows-agent-deployment-msi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
