fleet/docs/scripts.yml
Eric 048fcd13ed
Website: Add controls library pages (#33143)
Related to: https://github.com/fleetdm/confidential/issues/10737

Changes:
- Added `docs/scripts.yml`, a YAML file that contains a list of scripts
- Added `docs/mdm-commands.yml`, a YAML file that contains Windows and
Apple MDM commands
- Added `/mdm-commands`, a page that contains a list of MDM commands for
Windows and Apple commands
- Added `/scripts`, a page that contains a list of scripts
- Updated the `<docs-nav-and-search>` component to have a link to the
controls library, and reordered the lists.
- Updated the build static content script to add the scripts and mdm
commands from scripts.yml and mdm-commands.yml to the website's
`builtStaticContent` configuration.
- Updated the layout of the os-settings page to match the latest
wireframes
2025-09-19 12:02:55 -05:00

1240 lines
44 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#
# ╔╦╗╔═╗╔═╗╔═╗╔═╗
# ║║║╠═╣║ ║ ║╚═╗
# ╩ ╩╩ ╩╚═╝╚═╝╚═╝
- name: Collect fleetd logs
platform: macos
description: Copies logs from the fleetd agent to a shared folder.
script: |
cp /var/log/orbit/orbit.stderr.log ~/Library/Logs/Fleet/fleet-desktop.log /Users/Shared
echo "Successfully copied fleetd logs to the /Users/Shared folder."
echo "To retrieve logs, ask the end user to open Finder and in the menu bar select Go > Go to Folder."
echo "Then, ask the end user to type in /Users/Shared, press Return, and locate orbit.stderr.log (Orbit logs) and fleet-desktop.log (Fleet Desktop logs) files."
- name: Create conditional access allow file.
platform: macos
description: Create an allow file used for Microsoft Entra conditional access.
script: |
#!/bin/bash
# Script to check for entra-conditional-access-allow file and create it if needed
# Target location: /var/fleet/entra-conditional-access-allow
FILE_PATH="/var/fleet/entra-conditional-access-allow"
DIR_PATH="/var/fleet"
# Check if the directory exists, create if it doesn't
if [ ! -d "$DIR_PATH" ]; then
echo "Directory $DIR_PATH does not exist. Creating it..."
sudo mkdir -p "$DIR_PATH"
fi
# Check if the file exists
if [ -f "$FILE_PATH" ]; then
echo "File $FILE_PATH already exists."
# Check current permissions
CURRENT_PERMS=$(stat -f "%Lp" "$FILE_PATH" 2>/dev/null)
if [ "$CURRENT_PERMS" != "644" ]; then
echo "Current permissions: $CURRENT_PERMS. Updating to 644..."
sudo chmod 644 "$FILE_PATH"
echo "Permissions updated to 644."
else
echo "Permissions are already set to 644."
fi
else
echo "File $FILE_PATH does not exist. Creating it..."
# Create the file using touch
sudo touch "$FILE_PATH"
# Set permissions to 644
sudo chmod 644 "$FILE_PATH"
echo "File created with permissions 644."
fi
# Verify the file and permissions
if [ -f "$FILE_PATH" ]; then
FINAL_PERMS=$(stat -f "%Lp" "$FILE_PATH" 2>/dev/null)
echo "✓ File exists at: $FILE_PATH"
echo "✓ Permissions: $FINAL_PERMS"
else
echo "✗ Error: Failed to create file"
exit 1
fi
- name: Refetch host
platform: macos
description: A script to refetch host details using Fleet's device authentication token. This script reads the device token from /opt/orbit/identifier and triggers a refetch.
script: |
#!/bin/bash
set -euo pipefail # Exit on error, undefined vars, and pipe failures
# Configuration
IDENTIFIER_FILE="/opt/orbit/identifier"
FLEET_URL="https://dogfood.fleetdm.com" # Set this environment variable or modify the script
LOG_LEVEL="${LOG_LEVEL:-info}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging function
log() {
local level="$1"
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
case "$level" in
"error")
echo -e "${timestamp} [${RED}ERROR${NC}] $message" >&2
;;
"warn")
echo -e "${timestamp} [${YELLOW}WARN${NC}] $message" >&2
;;
"info")
echo -e "${timestamp} [${GREEN}INFO${NC}] $message"
;;
"debug")
if [[ "$LOG_LEVEL" == "debug" ]]; then
echo -e "${timestamp} [DEBUG] $message"
fi
;;
esac
}
# Function to check if a command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Function to validate URL format
validate_url() {
local url="$1"
if [[ ! "$url" =~ ^https?://[^[:space:]]+$ ]]; then
return 1
fi
return 0
}
# Function to read device token
read_device_token() {
local token_file="$1"
if [[ ! -f "$token_file" ]]; then
log error "Device token file not found: $token_file"
return 1
fi
if [[ ! -r "$token_file" ]]; then
log error "Cannot read device token file: $token_file (permission denied)"
return 1
fi
local token
token=$(cat "$token_file" 2>/dev/null)
if [[ -z "$token" ]]; then
log error "Device token file is empty: $token_file"
return 1
fi
# Basic validation - Fleet device tokens should be non-empty strings
if [[ ${#token} -lt 10 ]]; then
log warn "Device token seems unusually short (${#token} characters)"
fi
echo "$token"
}
# Function to get host ID from device token
get_host_id() {
local fleet_url="$1"
local device_token="$2"
log debug "Attempting to get host ID using device token..."
# Use the device endpoint to get basic host information
local response
local http_code
response=$(curl -s -w "HTTPSTATUS:%{http_code}" \
-H "Accept: application/json" \
-H "User-Agent: fleet-refetch-script/1.0" \
--max-time 30 \
--retry 2 \
--retry-delay 1 \
"${fleet_url}/api/v1/fleet/device/${device_token}" 2>/dev/null)
if [[ $? -ne 0 ]]; then
log error "Failed to connect to Fleet server at $fleet_url"
return 1
fi
http_code=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
response_body=$(echo "$response" | sed -e 's/HTTPSTATUS:.*//g')
log debug "HTTP response code: $http_code"
if [[ "$http_code" -ne 200 ]]; then
log error "Failed to authenticate with device token (HTTP $http_code)"
if [[ "$http_code" -eq 401 ]]; then
log error "Device token appears to be invalid or expired"
elif [[ "$http_code" -eq 404 ]]; then
log error "Device not found or Fleet server endpoint not available"
fi
return 1
fi
# Extract host ID from JSON response
local host_id
if command_exists jq; then
host_id=$(echo "$response_body" | jq -r '.host.id' 2>/dev/null)
else
# Fallback: basic grep/sed extraction (less reliable but doesn't require jq)
host_id=$(echo "$response_body" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
fi
if [[ -z "$host_id" || "$host_id" == "null" ]]; then
log error "Could not extract host ID from response"
log debug "Response body: $response_body"
return 1
fi
echo "$host_id"
}
# Function to trigger device-level refetch using device token
trigger_device_refetch() {
local fleet_url="$1"
local device_token="$2"
log info "Triggering device refetch using device token..."
# Try device-specific refetch endpoint that may accept device tokens
local response
local http_code
# First attempt: Try device-specific refetch endpoint
response=$(curl -s -w "HTTPSTATUS:%{http_code}" \
-X POST \
-H "Accept: application/json" \
-H "User-Agent: fleet-refetch-script/1.0" \
--max-time 30 \
--retry 2 \
--retry-delay 1 \
"${fleet_url}/api/v1/fleet/device/${device_token}/refetch" 2>/dev/null)
if [[ $? -ne 0 ]]; then
log warn "Failed to connect to device-specific refetch endpoint, trying alternative..."
return 1
fi
http_code=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
response_body=$(echo "$response" | sed -e 's/HTTPSTATUS:.*//g')
log debug "Device refetch HTTP response code: $http_code"
case "$http_code" in
200|202)
log info "Device refetch triggered successfully"
return 0
;;
404)
log warn "Device-specific refetch endpoint not available, trying alternative method..."
return 1
;;
401|403)
log error "Device token authentication failed for refetch"
return 1
;;
*)
log warn "Device refetch returned HTTP $http_code, trying alternative method..."
return 1
;;
esac
}
# Function to trigger refetch via orbit/fleetd ping mechanism
trigger_orbit_ping() {
local fleet_url="$1"
local device_token="$2"
log info "Attempting to trigger refetch via orbit ping mechanism..."
local response
local http_code
# Try the orbit device ping endpoint which may trigger a refetch
response=$(curl -s -w "HTTPSTATUS:%{http_code}" \
-X POST \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "User-Agent: fleet-refetch-script/1.0" \
-d '{"node_key": "'$device_token'", "refetch_requested": true}' \
--max-time 30 \
--retry 2 \
--retry-delay 1 \
"${fleet_url}/api/fleet/orbit/ping" 2>/dev/null)
if [[ $? -ne 0 ]]; then
log warn "Failed to connect to orbit ping endpoint"
return 1
fi
http_code=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
response_body=$(echo "$response" | sed -e 's/HTTPSTATUS:.*//g')
log debug "Orbit ping HTTP response code: $http_code"
case "$http_code" in
200)
log info "Orbit ping successful - refetch may have been triggered"
return 0
;;
404)
log warn "Orbit ping endpoint not available"
return 1
;;
401|403)
log warn "Authentication failed for orbit ping"
return 1
;;
*)
log warn "Orbit ping returned HTTP $http_code"
return 1
;;
esac
}
# Function to simulate osquery check-in to trigger refetch
trigger_osquery_checkin() {
local fleet_url="$1"
local device_token="$2"
log info "Attempting to trigger refetch via osquery distributed read..."
local response
local http_code
# Simulate an osquery distributed read which should trigger refetch if requested
response=$(curl -s -w "HTTPSTATUS:%{http_code}" \
-X POST \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "User-Agent: fleet-refetch-script/1.0" \
-d '{"node_key": "'$device_token'"}' \
--max-time 30 \
--retry 2 \
--retry-delay 1 \
"${fleet_url}/api/v1/osquery/distributed/read" 2>/dev/null)
if [[ $? -ne 0 ]]; then
log warn "Failed to connect to osquery distributed endpoint"
return 1
fi
http_code=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
response_body=$(echo "$response" | sed -e 's/HTTPSTATUS:.*//g')
log debug "Osquery distributed read HTTP response code: $http_code"
case "$http_code" in
200)
# Check if refetch queries were returned
if echo "$response_body" | grep -q "SELECT\|osquery_info\|system_info"; then
log info "Osquery check-in successful - refetch queries may have been delivered"
return 0
else
log info "Osquery check-in successful but no refetch queries detected"
return 1
fi
;;
404)
log warn "Osquery distributed endpoint not available"
return 1
;;
401|403)
log warn "Authentication failed for osquery distributed read"
return 1
;;
*)
log warn "Osquery distributed read returned HTTP $http_code"
return 1
;;
esac
}
# Main refetch function that tries multiple approaches
trigger_refetch() {
local fleet_url="$1"
local device_token="$2"
local host_id="$3"
log info "Attempting to trigger host refetch using device token..."
# Try multiple approaches in order of preference
# Method 1: Device-specific refetch endpoint
if trigger_device_refetch "$fleet_url" "$device_token"; then
return 0
fi
# Method 2: Orbit ping mechanism
if trigger_orbit_ping "$fleet_url" "$device_token"; then
return 0
fi
# Method 3: Osquery distributed read (may trigger refetch)
if trigger_osquery_checkin "$fleet_url" "$device_token"; then
return 0
fi
# If all methods failed, provide guidance
log error "All refetch methods failed with the device token"
log error ""
log error "POSSIBLE SOLUTIONS:"
log error "1. The device might not have Fleet Desktop installed"
log error "2. Try restarting the fleetd/orbit service on this host to trigger a natural check-in"
log error "3. Use an API token with admin/maintainer privileges instead:"
log error " curl -X POST -H 'Authorization: Bearer YOUR_API_TOKEN' \\"
log error " '${fleet_url}/api/v1/fleet/hosts/${host_id}/refetch'"
return 1
}
# Function to display usage
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Refetch host details using Fleet's device authentication token.
OPTIONS:
-u, --url URL Fleet server URL (can also be set via FLEET_URL env var)
-f, --file FILE Path to device token file (default: /opt/orbit/identifier)
-v, --verbose Enable debug logging
-h, --help Show this help message
EXAMPLES:
# Basic usage with Fleet URL as environment variable
export FLEET_URL="https://fleet.example.com"
$0
# Specify Fleet URL directly
$0 --url "https://fleet.example.com"
# Use custom token file location
$0 --url "https://fleet.example.com" --file "/custom/path/to/token"
# Enable verbose logging
$0 --url "https://fleet.example.com" --verbose
NOTES:
- The device must have Fleet Desktop installed
- The device token is read from $IDENTIFIER_FILE by default
- The Fleet server URL must include the protocol (http:// or https://)
- This script requires curl to be installed
- Optional: jq for better JSON parsing (will fall back to basic parsing if not available)
- The refetch operation may require elevated privileges depending on Fleet configuration
EOF
}
# Main function
main() {
local fleet_url="$FLEET_URL"
local token_file="$IDENTIFIER_FILE"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-u|--url)
fleet_url="$2"
shift 2
;;
-f|--file)
token_file="$2"
shift 2
;;
-v|--verbose)
LOG_LEVEL="debug"
shift
;;
-h|--help)
usage
exit 0
;;
*)
log error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Validation
if [[ -z "$fleet_url" ]]; then
log error "Fleet server URL is required"
log error "Set FLEET_URL environment variable or use --url option"
usage
exit 1
fi
if ! validate_url "$fleet_url"; then
log error "Invalid Fleet server URL: $fleet_url"
log error "URL must start with http:// or https://"
exit 1
fi
# Remove trailing slash from URL
fleet_url="${fleet_url%/}"
# Check dependencies
if ! command_exists curl; then
log error "curl is required but not installed"
exit 1
fi
if ! command_exists jq; then
log warn "jq not found - will use basic JSON parsing (less reliable)"
fi
log info "Starting Fleet host refetch process..."
log debug "Fleet URL: $fleet_url"
log debug "Token file: $token_file"
# Read device token
log info "Reading device authentication token..."
local device_token
if ! device_token=$(read_device_token "$token_file"); then
exit 1
fi
log debug "Device token length: ${#device_token} characters"
# Get host ID
log info "Authenticating with Fleet server..."
local host_id
if ! host_id=$(get_host_id "$fleet_url" "$device_token"); then
exit 1
fi
log info "Successfully authenticated - Host ID: $host_id"
# Trigger refetch
if trigger_refetch "$fleet_url" "$device_token" "$host_id"; then
log info "Host refetch completed successfully"
log info "Note: It may take a few moments for the updated data to be available"
exit 0
else
log error "Host refetch failed"
exit 1
fi
}
# Run main function with all arguments
main "$@"
- name: Uninstall fleetd
platform: macos
description: Removes the fleetd agent from a macOS device.
script: |
#!/bin/sh
# Please don't delete. This script is referenced in the guide here: https://fleetdm.com/guides/how-to-uninstall-fleetd
if [ $(id -u) -ne 0 ]; then
echo "Please run as root"
exit 1
fi
function remove_fleet {
set -x
rm -rf /Library/LaunchDaemons/com.fleetdm.orbit.plist /var/lib/orbit /usr/local/bin/orbit /var/log/orbit /opt/orbit/
pkgutil --forget com.fleetdm.orbit.base.pkg || true
launchctl stop com.fleetdm.orbit
launchctl unload /Library/LaunchDaemons/com.fleetdm.orbit.plist
pkill fleet-desktop || true
# Check MDM status on a macOS device
mdm_status=$(profiles status -type enrollment)
# Check for MDM enrollment status and cleanup enrollment profile
if echo "$mdm_status" | grep -q "MDM enrollment: Yes"; then
echo "This Mac is MDM enrolled. Removing enrollment profile."
profiles remove -identifier com.fleetdm.fleet.mdm.apple
elif echo "$mdm_status" | grep -q "MDM enrollment: No"; then
echo "This Mac is not MDM enrolled."
else
echo "MDM status is unknown."
fi
}
if [ "$1" = "remove" ]; then
# We are in the detached child process
# Give the parent process time to report the success before removing
echo "inside remove process" >>/tmp/fleet_remove_log.txt
sleep 15
# We are root
remove_fleet >>/tmp/fleet_remove_log.txt 2>&1
else
# We are in the parent shell, start the detached child and return success
echo "Removing fleetd, system will be unenrolled in 15 seconds..."
echo "Executing detached child process"
# We are root
bash -c "bash $0 remove >/dev/null 2>/dev/null </dev/null &"
fi
#
# ╦ ╦╦╔╗╔╔╦╗╔═╗╦ ╦╔═╗
# ║║║║║║║ ║║║ ║║║║╚═╗
# ╚╩╝╩╝╚╝═╩╝╚═╝╚╩╝╚═╝
- name: Create admin user
platform: windows
description: Creates an Admin user on Windows device.
script: |
$Username = "IT admin"
$Password = ConvertTo-SecureString "StrongPassword123!" -AsPlainText -Force
# Create the local user account
New-LocalUser -Name $Username -Password $Password -FullName "Fleet IT admin"
-Description "Admin account used to login when the end user forgets their
password or the host is returned to Fleet."
-AccountNeverExpires
# Add the user to the Administrators group
Add-LocalGroupMember -Group "Administrators" -Member $Username
- name: Enable Windows defender
platform: windows
description: Enable Windows Defender anti-virus.
script: |
# Based on commands found here: https://support.huntress.io/hc/en-us/articles/4402989131283-Enable-Microsoft-Defender-via-PowerShell
# Enable Real-Time Monitoring
Set-MpPreference -DisableRealtimeMonitoring $false
# Enable IOAV Protection
Set-MpPreference -DisableIOAVProtection $false
# Create Registry Key for Real-Time Protection
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender" -Name "Real-Time Protection" -Force
# Enable Behavior Monitoring
New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection" -Name "DisableBehaviorMonitoring" -Value 0 -PropertyType DWORD -Force
# Enable On-Access Protection
New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection" -Name "DisableOnAccessProtection" -Value 0 -PropertyType DWORD -Force
# Ensure Scans Run When Real-Time Protection is Enabled
New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection" -Name "DisableScanOnRealtimeEnable" -Value 0 -PropertyType DWORD -Force
# Ensure AntiSpyware is Enabled
New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender" -Name "DisableAntiSpyware" -Value 0 -PropertyType DWORD -Force
# Start Windows Defender Services
Start-Service -Name WinDefend
Start-Service -Name WdNisSvc
- name: Turn off Windows MDM
platform: windows
description: Unenrolls a Windows device from an MDM solution.
script: |
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class MdmRegistration
{
[DllImport("mdmregistration.dll", SetLastError = true)]
public static extern int UnregisterDeviceWithManagement(IntPtr pDeviceID);
public static int UnregisterDevice()
{
return UnregisterDeviceWithManagement(IntPtr.Zero);
}
}
"@ -Language CSharp
try {
$result = [MdmRegistration]::UnregisterDevice()
if ($result -ne 0) {
throw "UnregisterDeviceWithManagement failed with error code: $result"
}
Write-Host "Device unregistration called successfully."
} catch {
Write-Error "Error calling UnregisterDeviceWithManagement: $_"
}
- name: Uninstall fleetd
platform: windows
description: Removes the fleetd agent from a Windows device.
script: |
function Test-Administrator
{
[OutputType([bool])]
param()
process {
[Security.Principal.WindowsPrincipal]$user = [Security.Principal.WindowsIdentity]::GetCurrent();
return $user.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator);
}
}
# borrowed from Jeffrey Snover http://blogs.msdn.com/powershell/archive/2006/12/07/resolve-error.aspx
function Resolve-Error-Detailed($ErrorRecord = $Error[0]) {
$error_message = "========== ErrorRecord:{0}ErrorRecord.InvocationInfo:{1}Exception:{2}"
$formatted_errorRecord = $ErrorRecord | format-list * -force | out-string
$formatted_invocationInfo = $ErrorRecord.InvocationInfo | format-list * -force | out-string
$formatted_exception = ""
$Exception = $ErrorRecord.Exception
for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException)) {
$formatted_exception += ("$i" * 70) + "-----"
$formatted_exception += $Exception | format-list * -force | out-string
$formatted_exception += "-----"
}
return $error_message -f $formatted_errorRecord, $formatted_invocationInfo, $formatted_exception
}
#Stops Orbit service and related processes
function Stop-Orbit {
# Stop Service
Stop-Service -Name "Fleet osquery" -ErrorAction "Continue"
Start-Sleep -Milliseconds 1000
# Ensure that no process left running
Get-Process -Name "orbit" -ErrorAction "SilentlyContinue" | Stop-Process -Force
Get-Process -Name "osqueryd" -ErrorAction "SilentlyContinue" | Stop-Process -Force
Get-Process -Name "fleet-desktop" -ErrorAction "SilentlyContinue" | Stop-Process -Force
Start-Sleep -Milliseconds 1000
}
#Remove Orbit footprint from registry and disk
function Force-Remove-Orbit {
try {
#Stoping Orbit
Stop-Orbit
#Remove Service
$service = Get-WmiObject -Class Win32_Service -Filter "Name='Fleet osquery'"
if ($service) {
$service.delete() | Out-Null
}
#Removing Program files entries
$targetPath = $Env:Programfiles + "\\Orbit"
Remove-Item -LiteralPath $targetPath -Force -Recurse -ErrorAction "Continue"
#Remove HKLM registry entries
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Recurse -ErrorAction "SilentlyContinue" | Where-Object {($_.ValueCount -gt 0)} | ForEach-Object {
# Filter for osquery entries
$properties = Get-ItemProperty $_.PSPath -ErrorAction "SilentlyContinue" | Where-Object {($_.DisplayName -eq "Fleet osquery")}
if ($properties) {
#Remove Registry Entries
$regKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + $_.PSChildName
Get-Item $regKey -ErrorAction "SilentlyContinue" | Remove-Item -Force -ErrorAction "SilentlyContinue"
return
}
}
# Write success log
"Fleetd successfully removed at $(Get-Date)" | Out-File -Append -FilePath "$env:TEMP\fleet_remove_log.txt"
}
catch {
Write-Host "There was a problem running Force-Remove-Orbit"
Write-Host "$(Resolve-Error-Detailed)"
# Write error log
"Error removing fleetd at $(Get-Date): $($Error[0])" | Out-File -Append -FilePath "$env:TEMP\fleet_remove_log.txt"
return $false
}
return $true
}
function Main {
try {
# Is Administrator check
if (-not (Test-Administrator)) {
Write-Host "Please run this script with admin privileges."
Exit -1
}
Write-Host "About to uninstall fleetd..."
if ($args[0] -eq "remove") {
# "remove" is received as argument to the script when called as the
# sub-process that will actually remove the fleet agent.
# Log the start of removal process
"Starting removal process at $(Get-Date)" | Out-File -Append -FilePath "$env:TEMP\fleet_remove_log.txt"
# sleep to give time to fleetd to send the script results to Fleet
Start-Sleep -Seconds 20
if (Force-Remove-Orbit) {
Write-Host "fleetd was uninstalled."
Exit 0
} else {
Write-Host "There was a problem uninstalling fleetd."
Exit -1
}
} else {
# when this script is executed from fleetd, it does not immediately
# remove the agent. Instead, it starts a new detached process that
# will do the actual removal.
Write-Host "Removing fleetd, system will be unenrolled in 20 seconds..."
Write-Host "Executing detached child process"
$execName = $MyInvocation.ScriptName
$proc = Start-Process -PassThru -FilePath "powershell" -WindowStyle Hidden -ArgumentList "-MTA", "-ExecutionPolicy", "Bypass", "-File", "$execName remove"
# Log the process ID
"Started removal process with ID: $($proc.Id) at $(Get-Date)" | Out-File -Append -FilePath "$env:TEMP\fleet_remove_log.txt"
Start-Sleep -Seconds 5 # give time to process to start running
Write-Host "Removal process started: $($proc.Id)."
}
} catch {
Write-Host "Error: Entry point"
Write-Host "$(Resolve-Error-Detailed)"
Exit -1
}
}
# Execute the script with arguments passed to it
Main $args[0]
- name: Uninstall Slack
platform: windows
description: Removes Slack from a Windows device.
script: |
# Slack Uninstall Script
# This script handles both MSI and EXE installations, including per-user installations
$softwareName = "Slack"
$exitCode = 0
$uninstalled = $false
Write-Host "Starting Slack uninstallation process..."
# Function to uninstall MSI packages
function Remove-SlackMSI {
Write-Host "Checking for MSI-based Slack installations..."
# Find all Slack MSI products
$msiProducts = Get-WmiObject -Class Win32_Product -Filter "Name LIKE '%Slack%'" -ErrorAction SilentlyContinue
if ($msiProducts) {
foreach ($product in $msiProducts) {
Write-Host "Found MSI: $($product.Name) - Version: $($product.Version)"
Write-Host "Attempting to uninstall MSI..."
try {
$result = $product.Uninstall()
if ($result.ReturnValue -eq 0) {
Write-Host "Successfully uninstalled MSI: $($product.Name)"
return $true
} else {
Write-Host "MSI uninstall returned code: $($result.ReturnValue)"
}
} catch {
Write-Host "Error uninstalling MSI: $_"
}
}
}
# Also try using msiexec with product codes from registry
$msiKeys = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
foreach ($keyPath in $msiKeys) {
$keys = Get-ChildItem -Path $keyPath -ErrorAction SilentlyContinue |
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue }
foreach ($key in $keys) {
if ($key.DisplayName -like "*Slack*" -and $key.PSChildName -match '^{[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}}$') {
Write-Host "Found MSI product code: $($key.PSChildName)"
Write-Host "Attempting msiexec uninstall..."
$msiArgs = @("/x", $key.PSChildName, "/qn", "/norestart", "REBOOT=ReallySuppress")
$process = Start-Process -FilePath "msiexec.exe" -ArgumentList $msiArgs -Wait -PassThru -NoNewWindow
if ($process.ExitCode -eq 0) {
Write-Host "Successfully uninstalled via msiexec"
return $true
} else {
Write-Host "msiexec returned exit code: $($process.ExitCode)"
}
}
}
}
return $false
}
# Function to uninstall EXE-based installations
function Remove-SlackEXE {
Write-Host "Checking for EXE-based Slack installations..."
$uninstallKeys = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
$foundAny = $false
foreach ($keyPath in $uninstallKeys) {
$keys = Get-ChildItem -Path $keyPath -ErrorAction SilentlyContinue |
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue }
foreach ($key in $keys) {
if ($key.DisplayName -like "*Slack*") {
$foundAny = $true
Write-Host "Found: $($key.DisplayName) at $($keyPath)"
if ($key.UninstallString) {
# Extract the executable path and arguments
$uninstallString = $key.UninstallString
$exePath = ""
$arguments = ""
if ($uninstallString -match '^"([^"]+)"(.*)') {
$exePath = $matches[1]
$arguments = $matches[2].Trim()
} elseif ($uninstallString -match '^([^\s]+)(.*)') {
$exePath = $matches[1]
$arguments = $matches[2].Trim()
}
Write-Host "Uninstall executable: $exePath"
# For Slack, common silent parameters
$silentParams = @(
"--uninstall --force-uninstall",
"--uninstall",
"/S",
"/SILENT",
"-s"
)
# First try QuietUninstallString if available
if ($key.QuietUninstallString) {
Write-Host "Trying QuietUninstallString..."
$process = Start-Process -FilePath "cmd.exe" -ArgumentList "/c `"$($key.QuietUninstallString)`"" -Wait -PassThru -NoNewWindow
if ($process.ExitCode -eq 0) {
Write-Host "Successfully uninstalled using QuietUninstallString"
return $true
}
}
# Try each silent parameter
foreach ($param in $silentParams) {
Write-Host "Trying with parameters: $param"
try {
$fullArgs = if ($arguments) { "$arguments $param" } else { $param }
$process = Start-Process -FilePath $exePath -ArgumentList $fullArgs -Wait -PassThru -NoNewWindow -ErrorAction Stop
if ($process.ExitCode -eq 0) {
Write-Host "Successfully uninstalled with parameters: $param"
return $true
} else {
Write-Host "Exit code: $($process.ExitCode)"
}
} catch {
Write-Host "Error: $_"
}
}
}
}
}
}
if (-not $foundAny) {
Write-Host "No EXE-based Slack installations found in registry"
}
return $false
}
# Function to kill Slack processes
function Stop-SlackProcesses {
Write-Host "Checking for running Slack processes..."
$processes = Get-Process -Name "Slack*" -ErrorAction SilentlyContinue
if ($processes) {
Write-Host "Found $($processes.Count) Slack process(es). Attempting to stop..."
foreach ($proc in $processes) {
try {
$proc | Stop-Process -Force -ErrorAction Stop
Write-Host "Stopped process: $($proc.ProcessName) (PID: $($proc.Id))"
} catch {
Write-Host "Failed to stop process: $($proc.ProcessName) - $_"
}
}
Start-Sleep -Seconds 2
}
}
# Function to clean up Slack folders
function Remove-SlackFolders {
Write-Host "Cleaning up Slack folders..."
$foldersToRemove = @(
"$env:LOCALAPPDATA\Slack",
"$env:APPDATA\Slack",
"$env:ProgramFiles\Slack",
"${env:ProgramFiles(x86)}\Slack"
)
foreach ($folder in $foldersToRemove) {
if (Test-Path $folder) {
Write-Host "Removing folder: $folder"
try {
Remove-Item -Path $folder -Recurse -Force -ErrorAction Stop
Write-Host "Successfully removed: $folder"
} catch {
Write-Host "Failed to remove folder: $_"
}
}
}
}
# Main uninstallation logic
try {
# Stop Slack processes first
Stop-SlackProcesses
# Try MSI uninstallation first
$msiResult = Remove-SlackMSI
if ($msiResult) {
$uninstalled = $true
Write-Host "Slack uninstalled via MSI method"
}
# Try EXE uninstallation
$exeResult = Remove-SlackEXE
if ($exeResult) {
$uninstalled = $true
Write-Host "Slack uninstalled via EXE method"
}
# Clean up folders regardless of uninstall method success
Remove-SlackFolders
# Final verification
Start-Sleep -Seconds 3
$remainingInstalls = @(
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -like "*Slack*" }
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -like "*Slack*" }
Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -like "*Slack*" }
)
if ($remainingInstalls.Count -eq 0) {
Write-Host "Verification: No Slack installations found in registry"
$exitCode = 0
} elseif ($uninstalled) {
Write-Host "Warning: Some Slack registry entries remain, but uninstallation was attempted"
$exitCode = 0
} else {
Write-Host "Error: Slack uninstallation failed"
$exitCode = 1
}
} catch {
Write-Host "Critical error during uninstallation: $_"
$exitCode = 1
}
Write-Host "Slack uninstallation script completed with exit code: $exitCode"
Exit $exitCode
- name: Uninstall Zoom
platform: windows
description: Removes Zoom from a Windows device.
script: |
# Zoom Uninstall Script for Fleet
$exitCode = 0
# Kill Zoom processes
Get-Process -Name "Zoom*" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
# Method 1: Try CleanZoom.exe if provided with the script
$cleanZoom = "$PSScriptRoot\CleanZoom.exe"
if (Test-Path $cleanZoom) {
Write-Host "Using CleanZoom.exe"
& $cleanZoom /silent
$exitCode = $LASTEXITCODE
Exit $exitCode
}
# Method 2: Registry uninstall (both HKLM and HKCU)
$paths = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
$found = $false
foreach ($path in $paths) {
if (Test-Path $path) {
Get-ItemProperty $path -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -like "*Zoom*" } |
ForEach-Object {
$found = $true
Write-Host "Uninstalling: $($_.DisplayName)"
# Try QuietUninstallString
if ($_.QuietUninstallString) {
cmd /c "$($_.QuietUninstallString)" 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) { $exitCode = 0; return }
}
# Try UninstallString with silent flags
if ($_.UninstallString) {
# Zoom-specific silent uninstall
cmd /c "$($_.UninstallString) /uninstall /silent" 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) { $exitCode = 0; return }
# Generic silent
cmd /c "$($_.UninstallString) /S" 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) { $exitCode = 0; return }
}
}
}
}
if (-not $found) {
Write-Host "Zoom not found"
$exitCode = 0 # Not found = success for Fleet
}
Exit $exitCode
#
# ╦ ╦╔╗╔╦ ╦═╗ ╦
# ║ ║║║║║ ║╔╩╦╝
# ╩═╝╩╝╚╝╚═╝╩ ╚═
- name: Uninstall fleetd
platform: linux
description: Unintalls the fleetd agent on a Linux device.
script: |
#!/bin/bash
if [ $(id -u) -ne 0 ]; then
echo "Please run as root"
exit 1
fi
function remove_fleet {
set -x
systemctl stop orbit.service || true
systemctl disable orbit.service || true
rm -rf /var/lib/orbit /opt/orbit /var/log/orbit /usr/local/bin/orbit /etc/default/orbit /usr/lib/systemd/system/orbit.service
# Remove any package references
if command -v dpkg > /dev/null; then
dpkg --purge fleet-osquery || true
elif command -v rpm > /dev/null; then
rpm -e fleet-osquery || true
fi
# Kill any running Fleet processes
pkill -f fleet-desktop || true
# Reload systemd configuration
systemctl daemon-reload
echo "Fleetd has been successfully removed from the system."
}
if [ "$1" = "remove" ]; then
# We are in the detached child process
# Give the parent process time to report the success before removing
echo "inside remove process" >>/tmp/fleet_remove_log.txt
sleep 15
# We are root
remove_fleet >>/tmp/fleet_remove_log.txt 2>&1
else
# We are in the parent shell, start the detached child and return success
echo "Removing fleetd, system will be unenrolled in 15 seconds..."
echo "Executing detached child process"
# We are root
bash -c "bash $0 remove >/dev/null 2>/dev/null </dev/null &"
fi
- name: Set the root user default group to GID 0
platform: linux
description: Set the root users primary group to the group with GID 0
script: |
#!/bin/bash
usermod -g 0 root
- name: Configure nftables loopback traffic
platform: linux
description: Set up nftables rules to allow legitimate loopback traffic on the lo interface while dropping spoofed loopback packets from external interfaces.
script: |
#!/bin/bash
nft create table inet filter
nft create chain inet filter input { type filter hook input priority 0 \; }
nft create chain inet filter output { type filter hook output priority 0 \; }
# Then add the loopback rules:
nft add rule inet filter input iif lo accept
nft add rule inet filter output oif lo accept
nft add rule inet filter input ip saddr 127.0.0.0/8 counter drop
nft add rule inet filter input ip6 saddr ::1 counter drop
# To make rules persistent, save them:
nft list ruleset > /etc/nftables.conf
systemctl enable nftables
- name: Enable auditd service
platform: linux
description: Enable and start the auditd service to provide system auditing and logging of security-relevant events.
script: |
#!/bin/bash
systemctl --now enable auditd
- name: Set default inactivity period for user accounts
platform: linux
description: Configure the default number of days (30) after a password expires before a user account is disabled.
script: |
#!/bin/bash
useradd -D -f 30
- name: Set ownership and permissions on GRUB configuration
platform: linux
description: Configure /boot/grub/grub.cfg to be owned by root and inaccessible to non-privileged users.
script: |
#!/bin/bash
chown root:root /boot/grub/grub.cfg
chmod og-rwx /boot/grub/grub.cfg