How to Check USB Storage Status in Linux Using Shell Scripting

Removable storage devices like USB drives are indispensable tools for data transfer, backups, and system recovery. However, they also pose a serious security risk if left unmanaged—particularly in enterprise or MSP environments. Knowing whether USB storage access is enabled on your Linux systems is not just a matter of convenience; it’s a matter of policy enforcement, security compliance, and operational awareness. This post explores a shell script that automates the process of checking USB storage status in Linux and optionally integrates with NinjaOne to report findings.

Background

In Linux, control over USB storage access is primarily handled by kernel modules—specifically, the usb_storage and uas (USB Attached SCSI) drivers. System administrators can disable these drivers either by blacklisting them or by configuring them to execute harmless commands instead of loading.

The script reviewed here is built to inspect the /etc/modprobe.d directory, where such module-loading rules typically reside. It’s especially relevant in environments managed through NinjaOne, where centralized oversight and automated compliance checks are essential. Managed Service Providers (MSPs) and internal IT teams benefit from this kind of tooling to ensure that endpoints conform to corporate policies.

The Script:

#!/usr/bin/env bash

# Description: Checks if the linux machine currently allows access to USB storage devices and optionally saves the results to a custom field.
# 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).
#
#   The script checks the modprobe.d folder for any files that may block the USB storage driver or the USB Attached SCSI driver.
#   There are two methods of loading USB storage drivers that allow mounting of USB storage devices:
#       1. Implicit loading: The USB storage driver is loaded automatically when a USB storage device is connected.
#       2. Explicit loading: The USB storage driver is loaded manually by a user, program, or script.
#
# Release Notes: Initial Release
#
# Preset Parameter: -CustomField "usbStorageStatus"
#   The custom field to save the USB storage status to.
#
# Preset Parameter: --help
#   Displays the help menu.
#

# Functions
# Print an error message and exit with a specific status code
die() {
    local _ret="${2:-1}"
    test "${_PRINT_HELP:-no}" = yes && print_help >&2
    echo "$1" >&2
    exit "${_ret}"
}

print_help() {
    printf '\n\n%s\n\n' 'Usage: [-CustomField "custom_field_name"]'
    printf '  %s\n' '-CustomField'
    printf '    The custom field to save the USB storage status to.\n'
}

# Parse the command-line arguments
parse_commandline() {
    while test $# -gt 0; do
        _key="$1"
        case "$_key" in
        -CustomField | --CustomField | -customfield | --customfield | -cf | --cf)
            _arg_customfield="CustomField"
            ;;
        --help | -help | -h | --h)
            _PRINT_HELP=yes die "" 0
            ;;
        *)
            _PRINT_HELP=yes die "[Error] Got an unexpected argument '$1'" 1
            ;;
        esac
        shift
    done
}

function SetCustomField() {
    if [[ -z "$1" ]] || [[ -z "$2" ]]; then
        echo "[Error] Missing required arguments."
        return 1
    fi
    local customfieldName=$1
    local customfieldValue=$2
    if [ -f "${NINJA_DATA_PATH}/ninjarmm-cli" ]; then
        if [ -x "${NINJA_DATA_PATH}/ninjarmm-cli" ]; then
            if "$NINJA_DATA_PATH"/ninjarmm-cli get "$customfieldName" >/dev/null; then
                # check if the value is greater than 10000 characters
                if [ ${#customfieldValue} -gt 10000 ]; then
                    echo "[Warn] Custom field value is greater than 10000 characters" >&2
                    return 1
                fi
                if ! echo "${customfieldValue::10000}" | "$NINJA_DATA_PATH"/ninjarmm-cli set --stdin "$customfieldName"; then
                    echo "[Warn] Failed to set custom field" >&2
                    return 1
                else
                    echo "[Info] Custom field value set successfully"
                fi
            else
                echo "[Warn] Custom Field ($customfieldName) does not exist or agent does not have permission to access it" >&2
                return 1
            fi
        else
            echo "[Warn] ninjarmm-cli is not executable" >&2
            return 1
        fi
    else
        echo "[Warn] ninjarmm-cli does not exist" >&2
        return 1
    fi
}

parse_commandline "$@"

# If script form variables are used, replace the command line parameters with their value.
if [[ -n "${customField}" ]]; then
    _arg_customfield="${customField}"
fi

# Variables
# Modprobe.d folder
modprobFolder="/etc/modprobe.d"

# Check if we have read access to the modprobe.d folder
if ! [ -r "${modprobFolder}" ]; then
    if [[ -n "${_arg_customfield}" ]]; then
        SetCustomField "${_arg_customfield}" "Unable to Determine"
    fi
    die "[Error] User ${USER} has no read access to modprobe.d folder" 1
fi

# Check if modprobe.d folder does not exist
if ! [ -d "${modprobFolder}" ]; then
    if [[ -n "${_arg_customfield}" ]]; then
        SetCustomField "${_arg_customfield}" "Unable to Determine"
    fi
    die "[Error] Modprobe folder does not exist" 1
fi

# Set default values
UsbStorage="Enabled"
usb_storage_status="Enabled"
uas_status="Enabled"
usb_storage_driver_status="Enabled"
uas_driver_status="Enabled"

# Check if usb_storage or uas is blocked in other files
for file in "${modprobFolder}"/*; do

    if ! [ -r "${file}" ]; then
        echo "[Warn] User ${USER} has no read access to ${file}"
    fi

    # Check if implicit loading is blocked
    if grep -q "blacklist usb_storage" "${file}"; then
        echo "[Info] USB storage driver is blocked in ${file}"
        usb_storage_status="Disabled"
    fi
    # Check if implicit loading is blocked
    if grep -q "blacklist uas" "${file}"; then
        echo "[Info] USB Attached SCSI driver is blocked in ${file}"
        uas_status="Disabled"
    fi

    # Check if explicit loading is blocked
    if grep -q "install usb-storage /bin/true" "${file}"; then
        echo "[Info] USB storage driver is blocked in ${file}"
        usb_storage_driver_status="Disabled"
    fi
    # Check if explicit loading is blocked
    if grep -q "install uas /bin/true" "${file}"; then
        echo "[Info] USB Attached SCSI driver is blocked in ${file}"
        uas_driver_status="Disabled"
    fi
done

# Output the status of implicit loading
if [ "${usb_storage_status}" == "Disabled" ] && [ "${uas_status}" == "Disabled" ]; then
    echo "[Info] USB storage driver implicit loading: Blocked"
elif [ "${usb_storage_status}" == "Enabled" ] && [ "${uas_status}" == "Enabled" ]; then
    echo "[Info] USB storage driver implicit loading: Allowed"
else
    echo "[Info] USB storage driver implicit loading: Partially Allowed"
fi

# Output the status of explicit loading
if [ "${usb_storage_driver_status}" == "Disabled" ] && [ "${uas_driver_status}" == "Disabled" ]; then
    echo "[Info] USB storage driver manual loading: Blocked"
elif [ "${usb_storage_driver_status}" == "Enabled" ] && [ "${uas_driver_status}" == "Enabled" ]; then
    echo "[Info] USB storage driver manual loading: Allowed"
else
    echo "[Info] USB storage driver manual loading: Partially Allowed"
fi

# Determine the overall status of USB storage
if [ "${usb_storage_status}" == "Disabled" ] && [ "${uas_status}" == "Disabled" ] && [ "${usb_storage_driver_status}" == "Disabled" ] && [ "${uas_driver_status}" == "Disabled" ]; then
    UsbStorage="Disabled"
elif [ "${usb_storage_status}" == "Enabled" ] && [ "${uas_status}" == "Enabled" ] && [ "${usb_storage_driver_status}" == "Enabled" ] && [ "${uas_driver_status}" == "Enabled" ]; then
    UsbStorage="Enabled"
elif [ "${usb_storage_status}" == "Enabled" ] || [ "${uas_status}" == "Enabled" ] || [ "${usb_storage_driver_status}" == "Enabled" ] || [ "${uas_driver_status}" == "Enabled" ]; then
    UsbStorage="Partially Allowed"
else
    UsbStorage="Unable to Determine"
fi

echo "[Info] USB storage is ${UsbStorage}"

if [[ -n "${_arg_customfield}" ]]; then
    SetCustomField "${_arg_customfield}" "${UsbStorage}"
fi

 

Detailed Breakdown

This shell script performs several tasks in a structured and fault-tolerant way. Here’s a step-by-step breakdown:

  1. Command-Line Argument Parsing

    • Supports the optional -CustomField argument to specify a NinjaOne custom field.
    • Provides a –help option for usage guidance.
  2. Permission and Folder Checks

    • Validates the existence and readability of the /etc/modprobe.d directory.
    • If access is denied or the folder is missing, it reports “Unable to Determine” and, if configured, writes that to the NinjaOne custom field.
  3. Driver Status Evaluation

    • Iterates through each file in /etc/modprobe.d.
    • Looks for:
      1. blacklist usb_storage or blacklist uas (blocks implicit loading).
      2. install usb-storage /bin/true or install uas /bin/true (blocks explicit loading).
    • Tracks the status of each method per driver.
  4. Status Determination

    • Categorizes driver loading as Allowed, Blocked, or Partially Allowed.
    • Aggregates the result into a final status of Enabled, Disabled, or Partially Allowed.
  5. Integration with NinjaOne

    • If -CustomField is specified and the ninjarmm-cli tool is present, the script writes the USB storage status to the designated custom field.

Potential Use Cases

Case Study:

A healthcare MSP manages hundreds of Linux-based kiosks in clinics. Security policy dictates that USB storage should be disabled to prevent unauthorized data extraction. Using this script, the MSP can:

  • Deploy the script across all endpoints using NinjaOne.
  • Automatically log USB status into a custom field.
  • Generate compliance reports.
  • Alert technicians if any machine is out of policy.

This proactive compliance monitoring saves hours of manual auditing and ensures regulatory adherence (e.g., HIPAA).

Comparisons

Other Methods

Approach Pros Cons
Manual inspection (lsmod, grep) Immediate, CLI-native Time-consuming, non-scalable
Audit scripts in Python More flexible parsing Heavier dependencies
Endpoint Detection and Response (EDR) Centralized control, real-time High cost, often Windows-focused
This Shell Script Lightweight, integrates with NinjaOne May require sudo and Linux-savvy techs

This script’s niche is its simplicity, portability, and native integration with the NinjaOne ecosystem, making it ideal for environments where simplicity and automation trump customization.

Frequently Asked Questions

What Linux distributions are supported?

Most Debian- and Red Hat-based distributions with /etc/modprobe.d are compatible.

Does this script block USB storage?

No, it only checks the status. Blocking must be handled via system configuration.

What is ninjarmm-cli?

It’s NinjaOne’s command-line interface for reading/writing custom field data on endpoints.

What does “Partially Allowed” mean?

It indicates that some methods of driver loading are permitted while others are blocked.

Can I use this without NinjaOne?

Yes, but you won’t be able to save results to a custom field without ninjarmm-cli.

Implications

Understanding whether USB storage is allowed impacts your risk posture. Allowing unrestricted USB access increases the potential for data leakage, malware introduction, and accidental data transfers. In regulated industries—finance, healthcare, government—demonstrating control over USB access is often mandatory. This script enables such oversight in a minimally invasive, repeatable way.

Recommendations

  • Run the script with elevated permissions to ensure complete access to /etc/modprobe.d.
  • Standardize the custom field name across devices for unified reporting in NinjaOne.
  • Pair this script with remediation tools that enforce blocking if non-compliance is detected.
  • Schedule it as a recurring task within NinjaOne for periodic compliance validation.

Final Thoughts

Using shell scripting to check USB Storage Status in Linux provides a lightweight, auditable, and automatable method for endpoint compliance verification. When paired with NinjaOne, this script becomes a powerful tool for centralized policy enforcement and visibility. Whether you’re an MSP managing client fleets or an internal IT team securing corporate assets, this solution offers the right balance of simplicity and power.

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service delivery tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Categories:

You might also like

×

See NinjaOne in action!

By submitting this form, I accept NinjaOne's privacy policy.

NinjaOne Terms & Conditions

By clicking the “I Accept” button below, you indicate your acceptance of the following legal terms as well as our 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 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).