Managing antivirus configurations across devices is a critical task for IT professionals and Managed Service Providers (MSPs). Ensuring accurate and up-to-date antivirus information in device records is essential for maintaining a secure and compliant IT environment.
This blog explores a detailed bash script that simplifies adding or overriding antivirus details on macOS devices. Whether you need to update antivirus version data, change its operational status, or reset overrides entirely, this script streamlines the process with robust checks and automated actions.
Background
In dynamic IT environments, antivirus data can often become outdated or misaligned due to changes in software configurations, device migrations, or varying update cadences. MSPs managing large device fleets face challenges in maintaining consistency across antivirus records. Without a standardized way to override or update these details programmatically, manual intervention can lead to errors and inefficiencies.
This script addresses these challenges by offering a clear and structured approach to updating antivirus overrides in macOS environments. It allows IT professionals to:
- Add or update antivirus details in a JSON-based configuration.
- Remove outdated overrides from system records.
- Ensure antivirus status and state remain compliant with organizational policies.
The Script:
#!/usr/bin/env bash # # Description: Add an antivirus to the device details or override the existing antivirus information. # By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use. # Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. # Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. # Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. # Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. # Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. # Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. # EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA). # # Preset Parameter: --antivirusName "ReplaceMeWithYourDesiredName" # Name of the antivirus you would like to appear in the device details. # # Preset Parameter: --antivirusVersion "1.0.2" # Specify the version number of the antivirus. # # Preset Parameter: --antivirusStatus "Up-to-Date" # Specify whether the antivirus definitions are Up-to-Date, Out-of-Date, or Unknown. # # Preset Parameter: --antivirusState "ON" # Specify the current status of the antivirus. # # Preset Parameter: --removeOverride # Remove all existing overrides. # # Preset Parameter: --antivirusState # Append or update an existing override. # # Preset Parameter: --help # Displays a help menu. # # Release Notes: Initial Release # Initialize variables _arg_antivirusName= _arg_antivirusVersion= _arg_antivirusStatus= _arg_antivirusState= _arg_removeOverride="off" _arg_append="off" # Function to display help message print_help() { printf '\n\n%s\n\n' 'Usage: [--antivirusName|-n <arg>] [--antivirusVersion|-v <arg>] [--antivirusStatus|--status <arg>] [--antivirusState|--state <arg>] [--removeOverride|-r] [--append|-a] [--help|-h] ' printf '%s\n' 'Preset Parameter: --antivirusName "ReplaceMeWithYourDesiredName"' printf '\t%s\n' "Name of the antivirus you would like to appear in the device details." printf '%s\n' 'Preset Parameter: --antivirusVersion "1.0.2"' printf '\t%s\n' "Specify the version number of the antivirus." printf '%s\n' 'Preset Parameter: --antivirusStatus "Up-to-Date"' printf '\t%s\n' "Specify whether the antivirus definitions are Up-to-Date, Out-of-Date, or Unknown." printf '%s\n' 'Preset Parameter: --antivirusState "ON"' printf '\t%s\n' "Specify the current status of the antivirus." printf '%s\n' 'Preset Parameter: --removeOverride' printf '\t%s\n' "Remove all existing overrides." printf '%s\n' 'Preset Parameter: --antivirusState' printf '\t%s\n' "Append or update an existing override." printf '\n%s\n' 'Preset Parameter: --help' printf '\t%s\n' "Displays this help menu." } # Function to print an error message and exit die() { local _ret="${2:-1}" echo "$1" >&2 test "${_PRINT_HELP:-no}" = yes && print_help >&2 exit "${_ret}" } # Function to parse command line arguments parse_commandline() { while test $# -gt 0; do _key="$1" case "$_key" in --antivirusName | --antivirusname | --name | -n) test $# -lt 2 && die "Missing value for the argument '$_key'." 1 _arg_antivirusName=$2 shift ;; --antivirusName=*) _arg_antivirusName="${_key##--antivirusName=}" ;; --antivirusVersion | --antivirusversion | --version | -v) test $# -lt 2 && die "Missing value for the argument '$_key'." 1 _arg_antivirusVersion=$2 shift ;; --antivirusVersion=*) _arg_antivirusVersion="${_key##--antivirusVersion=}" ;; --antivirusStatus | --antivirusstatus | --status) test $# -lt 2 && die "Missing value for the argument '$_key'." 1 _arg_antivirusStatus=$2 shift ;; --antivirusStatus=*) _arg_antivirusStatus="${_key##--antivirusStatus=}" ;; --antivirusState | --antivirusstate | --state) test $# -lt 2 && die "Missing value for the argument '$_key'." 1 _arg_antivirusState=$2 shift ;; --antivirusState=*) _arg_antivirusState="${_key##--antivirusState=}" ;; --removeOverride | --remove | -r) _arg_removeOverride="on" ;; --append | --Append | -a) _arg_append="on" ;; --help | -h) _PRINT_HELP=yes die 0 ;; *) _PRINT_HELP=yes die "[Error] Got an unexpected argument '$1'" 1 ;; esac shift done } # Parse the command line arguments parse_commandline "$@" # Replace commandline parameters with script form variables if [[ -n $avName ]]; then _arg_antivirusName="$avName" fi if [[ -n $avVersion ]]; then _arg_antivirusVersion="$avVersion" fi if [[ -n $avStatus ]]; then _arg_antivirusStatus="$avStatus" fi if [[ -n $avState ]]; then _arg_antivirusState="$avState" fi if [[ -n $append && $append == "true" ]]; then _arg_append="on" fi if [[ -n $removeOverride && $removeOverride == "true" ]]; then _arg_removeOverride="on" fi # Ensure that removing an override and adding/updating an override are not done simultaneously if [[ $_arg_removeOverride == "on" && (-n $_arg_antivirusName || -n $_arg_antivirusState || -n $_arg_antivirusStatus || -n $_arg_antivirusVersion || $_arg_append == "on") ]]; then _PRINT_HELP=no die "[Error] Cannot remove an override and add an override at the same time." 1 fi # Check for required antivirus name and escape special characters if necessary reservedCharacters='[\"]' if [[ -z $_arg_antivirusName && $_arg_removeOverride != "on" ]]; then if [[ $_arg_append == "on" ]]; then _PRINT_HELP=yes die "[Error] Antivirus name was not given. The antivirus name is required when updating or adding a new override!" 1 else _PRINT_HELP=yes die "[Error] Antivirus name was not given. Antivirus name, state, and status are required when adding a new override!" 1 fi elif [[ -n $_arg_antivirusName && $_arg_antivirusName =~ $reservedCharacters ]]; then _arg_antivirusName=${_arg_antivirusName//\\/\\\\} _arg_antivirusName=${_arg_antivirusName//\"/\\\"} fi # Check for required antivirus status if [[ -z $_arg_antivirusStatus && $_arg_removeOverride != "on" && $_arg_append != "on" ]]; then _PRINT_HELP=yes die "[Error] Antivirus status was not given. Antivirus name, state, and status are required!" 1 fi # Validate antivirus status if [[ -n $_arg_antivirusStatus && $_arg_antivirusStatus != "Up-to-Date" && $_arg_antivirusStatus != "Out-of-Date" && $_arg_antivirusStatus != "Unknown" ]]; then _PRINT_HELP=no die "[Error] An invalid antivirus status of '$_arg_antivirusStatus' was given. Only the following statuses are valid. 'Up-to-Date', 'Out-of-Date', and 'Unknown'." 1 fi # Check for required antivirus state if [[ -z $_arg_antivirusState && $_arg_removeOverride != "on" && $_arg_append != "on" ]]; then _PRINT_HELP=yes die "[Error] Antivirus state was not given. Antivirus name, state, and status are required!" 1 else _arg_antivirusState=$(echo "$_arg_antivirusState" | tr '[:lower:]' '[:upper:]') fi # Validate antivirus state if [[ -n $_arg_antivirusState && $_arg_antivirusState != "ON" && $_arg_antivirusState != "OFF" && $_arg_antivirusState != "EXPIRED" && $_arg_antivirusState != "SNOOZED" && $_arg_antivirusState != "UNKNOWN" ]]; then _PRINT_HELP=no die "[Error] An invalid antivirus state of '$_arg_antivirusState' was given. Only the following states are valid. 'ON', 'OFF', 'EXPIRED', 'SNOOZED', and 'UNKNOWN'." 1 fi # Validate antivirus version if [[ -n $_arg_antivirusVersion && $_arg_antivirusVersion =~ [^0-9\.] ]]; then _PRINT_HELP=no die "[Error] The antivirus version given '$_arg_antivirusVersion' contains an invalid character. Only the following characters are allowed. '0-9' and '.'" 1 fi # Check if the removeOverride flag is set to "on" if [[ $_arg_removeOverride == "on" ]]; then echo "Removing override as requested." # Check if the override file exists if [[ ! -f "/Applications/NinjaRMMAgent/programdata/customization/av_override.json" ]]; then echo "No override present." exit 0 # Try to remove the override file and capture any error output elif output=$(rm "/Applications/NinjaRMMAgent/programdata/customization/av_override.json" 2>&1); then echo "Succesfully removed override!" exit 0 # Print an error message if the removal fails else echo "[Error] Failed to remove override!" echo "[Error] $output" fi fi # Check if the customization directory exists if [[ ! -d "/Applications/NinjaRMMAgent/programdata/customization" ]]; then echo "Creating customization folder at '/Applications/NinjaRMMAgent/programdata/customization'." # Try to create the customization directory and capture any error output if output=$(mkdir "/Applications/NinjaRMMAgent/programdata/customization" 2>&1); then echo "Folder created." else # Print an error message if the creation fails echo "[Error] Unable to create customization folder." >&2 echo "[Error] $output" >&2 exit 1 fi fi # Check if the append flag is set to "on" and the override JSON file exists if [[ $_arg_append == "on" && -f "/Applications/NinjaRMMAgent/programdata/customization/av_override.json" ]]; then # Extract antivirus names, versions, statuses and states from the JSON file avNames=$(grep "av_name" "/Applications/NinjaRMMAgent/programdata/customization/av_override.json" | tr -s " " | sed s/\"av_name\"://g | sed -e 's/^[[:space:]]*//' | sed 's/[\",]//g' | sed -e 's/[[:space:]]*$//') avVersions=$(grep "av_version" "/Applications/NinjaRMMAgent/programdata/customization/av_override.json" | tr -s " " | sed s/\"av_version\"://g | sed -e 's/^[[:space:]]*//' | sed 's/[\",]//g' | sed -e 's/[[:space:]]*$//') avStatuses=$(grep "av_status" "/Applications/NinjaRMMAgent/programdata/customization/av_override.json" | tr -s " " | sed s/\"av_status\"://g | sed -e 's/^[[:space:]]*//' | sed 's/[\",]//g' | sed -e 's/[[:space:]]*$//') avStates=$(grep "av_state" "/Applications/NinjaRMMAgent/programdata/customization/av_override.json" | tr -s " " | sed s/\"av_state\"://g | sed -e 's/^[[:space:]]*//' | sed 's/[\",]//g' | sed -e 's/[[:space:]]*$//') # Find the line number of the existing antivirus entry with the given name existingAV=$(echo "$avNames" | grep -n "$_arg_antivirusName" | sed 's/:.*//g') # Determine the desired antivirus status if [[ -n $_arg_antivirusStatus ]]; then desiredStatus=$_arg_antivirusStatus elif [[ -n $existingAV ]]; then desiredStatus=$(echo "$avStatuses" | sed -n "${existingAV}p") fi # Determine the desired antivirus state if [[ -n $_arg_antivirusState ]]; then desiredState=$_arg_antivirusState elif [[ -n $existingAV ]]; then desiredState=$(echo "$avStates" | sed -n "${existingAV}p") fi # Check if both status and state are provided if [[ -z $desiredStatus || -z $desiredState ]]; then _PRINT_HELP=no die "[Error] Antivirus state or status are missing from the override entry. Please provide both in addition to the antivirus name!" 1 fi # Update the existing antivirus entry if found if [[ -n $existingAV ]]; then echo "Attempting to update override." # Update antivirus version if provided if [[ -n $_arg_antivirusVersion ]]; then modified_json=$(awk -v target="$existingAV" -v value="$_arg_antivirusVersion" 'BEGIN { av_count = 1 } /av_version/ { if (av_count == target){ sub( /av_version.*/ , "av_version\": \""value"\"," ) } av_count++ }{ print }' "/Applications/NinjaRMMAgent/programdata/customization/av_override.json") if echo "$modified_json" >"/Applications/NinjaRMMAgent/programdata/customization/av_override.json"; then echo "Successfully updated the antivirus version!" else echo "[Error] Failed to update the antivirus version!" >&2 exit 1 fi fi # Update antivirus status if provided if [[ -n $_arg_antivirusStatus ]]; then modified_json=$(awk -v target="$existingAV" -v value="$_arg_antivirusStatus" 'BEGIN { av_count = 1 } /av_status/ { if (av_count == target){ sub( /av_status.*/ , "av_status\": \""value"\"," ) } av_count++ }{print}' "/Applications/NinjaRMMAgent/programdata/customization/av_override.json") if echo "$modified_json" >"/Applications/NinjaRMMAgent/programdata/customization/av_override.json"; then echo "Successfully updated the override status!" else echo "[Error] Failed to update the override status!" >&2 exit 1 fi fi # Update antivirus state if provided if [[ -n $_arg_antivirusState ]]; then modified_json=$(awk -v target="$existingAV" -v value="$_arg_antivirusState" 'BEGIN { av_count = 1 } /av_state/ { if (av_count == target){ sub( /av_state.*/ , "av_state\": \""value"\"" ) } av_count++ }{print}' "/Applications/NinjaRMMAgent/programdata/customization/av_override.json") if echo "$modified_json" >"/Applications/NinjaRMMAgent/programdata/customization/av_override.json"; then echo "Successfully updated the override state!" else echo "[Error] Failed to update the override state!" >&2 exit 1 fi fi exit fi # Print a message indicating that the script is attempting to append an override echo "Attempting to append override." # Initialize a counter for indexing i=1 # Initialize the JSON structure for antivirus overrides avOverrides="{ \"av_override\": [ " # Loop through each antivirus name for avName in $avNames; do # Extract the corresponding antivirus version, status and state for the current index avVersion=$(echo "$avVersions" | sed -n "${i}p") avStatus=$(echo "$avStatuses" | sed -n "${i}p") avState=$(echo "$avStates" | sed -n "${i}p") # Append the current antivirus entry to the JSON structure avOverrides+=" { \"av_name\": \"${avName}\", \"av_version\": \"${avVersion}\", \"av_status\": \"${avStatus}\", \"av_state\": \"${avState}\" }, " # Increment the counter for the next iteration i=$((i + 1)) done # Close the JSON structure avOverrides+=" { \"av_name\": \"${_arg_antivirusName}\", \"av_version\": \"${_arg_antivirusVersion}\", \"av_status\": \"${_arg_antivirusStatus}\", \"av_state\": \"${_arg_antivirusState}\" } ] } " # Attempt to write the JSON structure containing antivirus overrides to the specified file if echo "$avOverrides" >"/Applications/NinjaRMMAgent/programdata/customization/av_override.json"; then echo "Succesfully added override." exit else echo "[Error] Failed to add override." >&2 exit 1 fi fi # Check if the antivirus state or status arguments are missing if [[ -z $_arg_antivirusState || -z $_arg_antivirusStatus ]]; then _PRINT_HELP=no die "[Error] Antivirus name, state and status are required when adding a new override!" 1 fi # Construct the JSON string for the antivirus override JSON_STRING="{ \"av_override\": [ { \"av_name\": \"${_arg_antivirusName}\", \"av_version\": \"${_arg_antivirusVersion}\", \"av_status\": \"${_arg_antivirusStatus}\", \"av_state\": \"${_arg_antivirusState}\" } ] } " # Attempt to write the JSON string to the specified file if echo "$JSON_STRING" >"/Applications/NinjaRMMAgent/programdata/customization/av_override.json"; then # If the write operation is successful, print a success message echo "Succesfully created override." else # If the write operation fails, print an error message to standard error echo "[Error] Failed to create override." >&2 exit 1 fi
Save time with over 300+ scripts from the NinjaOne Dojo.
Detailed Breakdown
Let’s dissect the script and understand its core functionality:
Input Parameters and Validation
The script accepts several preset parameters via command-line arguments:
- –antivirusName defines the antivirus program name.
- –antivirusVersion specifies its version number.
- –antivirusStatus indicates whether the definitions are Up-to-Date, Out-of-Date, or Unknown.
- –antivirusState describes the program’s operational status (e.g., ON, OFF, EXPIRED).
- Flags like –removeOverride and –append determine whether to clear or update existing overrides.
The script rigorously validates inputs, ensuring no invalid data, such as unsupported characters or states, corrupts the override configuration.
Help and Error Handling
A print_help function provides detailed usage instructions for users, while error handling ensures incorrect usage or unexpected inputs are met with clear messages. Examples include notifying users about missing arguments or invalid antivirus states.
JSON Overrides Management
The script interacts with an override file located at /Applications/NinjaRMMAgent/programdata/customization/av_override.json. Depending on user actions:
- Remove Override: Deletes the override file if present, ensuring no residual configurations affect device behavior.
- Add/Update Override: Appends new antivirus data or modifies existing entries. This involves parsing the JSON file, locating specific entries, and updating fields as needed.
Directory Management
If the customization directory does not exist, the script creates it to ensure the override file can be stored reliably. Error messages are logged if directory creation fails.
Output and Feedback
Users receive detailed feedback at every step, such as successful updates or failures during JSON manipulation.
Potential Use Cases
Use Case: Updating Antivirus Details on a Managed macOS Device
Imagine an MSP managing a macOS fleet where a subset of devices has outdated antivirus records. Using this script, the IT administrator can:
- Append or update antivirus records with the latest version and status (–antivirusVersion “2.5.3” –antivirusStatus “Up-to-Date”).
- Remove obsolete overrides for devices no longer requiring antivirus configurations (–removeOverride).
- Automate configuration management across multiple devices by deploying the script via RMM tools like NinjaOne.
This streamlined process minimizes manual intervention and ensures accurate reporting in compliance audits.
Comparisons
Script vs. Manual JSON Editing
Editing JSON files manually is prone to syntax errors and lacks scalability. This script automates the process, reducing human error and enabling batch updates across devices.
Script vs. Custom Automation Tools
While larger enterprises may opt for complex automation platforms, this script provides a lightweight, cost-effective alternative for small to medium-sized IT environments.
FAQs
- Can this script be used on non-macOS systems?
No, this script is designed specifically for macOS, leveraging paths and commands unique to the platform. - What happens if an override file does not exist?
The script handles this gracefully by creating the required directory and file structure automatically. - Can I update only specific fields without affecting others?
Yes, the –append flag allows selective updates without overwriting unrelated fields. - How does the script handle conflicting actions, like adding and removing overrides simultaneously?
It explicitly prevents such conflicts, prompting users to choose one action at a time.
Implications
Accurate antivirus information contributes to a secure IT environment by enabling real-time monitoring and threat detection. By automating overrides, IT teams can focus on proactive security measures rather than manual data corrections. However, misuse of such scripts could inadvertently overwrite critical configurations, underscoring the need for careful use and thorough testing.
Recommendations
- Test the script in a controlled environment before deploying it widely.
- Keep a backup of original JSON files to recover from unintended changes.
- Use clear and consistent naming conventions for antivirus entries to avoid duplication.
- Leverage RMM tools like NinjaOne to distribute and execute this script across devices seamlessly.
Final Thoughts
This script exemplifies how automation can enhance efficiency and accuracy in IT management. For IT professionals using NinjaOne, the integration of such scripts into the platform’s toolkit offers a powerful way to manage macOS devices at scale. Whether you’re updating antivirus configurations or streamlining compliance processes, this script is a valuable addition to your IT arsenal.