Quick Links

Managing Blumira Sensor on Windows Hyper-V

Overview

Running the Docker-based Blumira sensor on a headless Ubuntu virtual machine (VM) is possible with Windows Hyper-V. The Manage-blumira.ps1 script we provide below allows you to deploy and manage the sensor and headless VM via PowerShell.

This script handles the following infrastructure requirements automatically:

  • Hyper-V NAT: It creates a dedicated internal network with a stable Static IP (192.168.200.2).
  • Port Forwarding: It maps host ports to the VM (Syslog 1514, Web 8080, SSH 2222).
  • Legacy Docker Support: It automatically patches the Ubuntu Kernel (GRUB) to support Cgroup v1, which is required for the sensor's internal Docker environment.
  • Self-Healing: It detects network and kernel issues and auto-reboots and resumes installation.

The following events automatically occur when using the script:

  1. The script downloads the Ubuntu Cloud Image.
  2. The script creates the VM and configures the NAT network.
  3. The VM boots, detects it needs a Kernel fix for Docker, and reboots itself automatically.
  4. About 60 seconds after the reboot, the VM resumes and installs the sensor.

Architecture and ports

The VM is hidden (headless) and runs as a background service via Hyper-V.

ServiceHost PortInternal VM PortProtocol
Syslog1514514TCP & UDP
Sensor Web80808080TCP
SSH222222TCP

Note: Traffic sent to your Windows host IP on port 1514 will automatically route to the sensor.

Before you begin

Before you run the PowerShell script, verify the following:

  • You are using a compatible OS (i.e., Windows 10 or 11 Pro, Enterprise, Education, or Server 2025 Datacenter). Home edition does not support full Hyper-V.
  • You have Administrator permissions, which are required to configure Hyper-V, NAT, and firewall rules.
  • Hyper-V is enabled. If Hyper-V is not enabled, the script will prompt you to enable it and reboot.

Save the following script as Manage-blumira.ps1 on your Windows machine: 

<#
.SYNOPSIS
    Hyper-V Headless Ubuntu VM Manager (Blumira Sensor Edition)
.DESCRIPTION
    Manages a native Hyper-V VM for the Blumira Sensor.
    Features:
    - Auto-enables Hyper-V.
    - Creates a dedicated NAT Network (Stable Static IPs).
    - Converts Ubuntu Cloud Image to VHDX automatically.
    - Applies Cgroup v1 fixes for legacy Docker support via GRUB drop-in.
    - Configures VM as a Router (IP Forwarding + Masquerading) for internal Docker traffic.
    - Accepts raw Blumira installation commands via -SensorCommand.
#>
param(
    [Parameter(Mandatory=$false)]
    [ValidateSet("Install", "Uninstall", "Status", "Auth", "Start", "Stop")]
    [string]$Mode = "Install",

    [Parameter(Mandatory=$false)]
    [string]$SensorCommand
)

# --- Configuration ---
$vmName        = "Blumira-Sensor-VM"
$baseDir       = "C:\VMs\$vmName"
$switchName    = "BlumiraNAT"
$natSubnet     = "192.168.200.0/24"
$gatewayIP     = "192.168.200.1"
$vmIP          = "192.168.200.2"
$vmDisk        = "$baseDir\ubuntu-disk.vhdx"
$seedDisk      = "$baseDir\seed.vhdx"
$ubuntuImgUrl  = "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
# We need qemu-img JUST for the one-time conversion of img -> vhdx
$qemuToolsUrl  = "https://qemu.weilnetz.de/w64/2025/qemu-w64-setup-20250826.exe" 
$username      = "blumira"
$password      = "SensorPass123!" # Complex password required by some policies

# No default URL. Must be provided via -SensorCommand
$blumiraUrl    = $null

# --- Argument Parsing ---
if (-not [string]::IsNullOrWhiteSpace($SensorCommand)) {
    # Regex to extract the URL from inside single quotes or standalone
    if ($SensorCommand -match "(https://storage\.googleapis\.com/[^']*)") {
        $blumiraUrl = $matches[1]
        Write-Host "Extracted Sensor URL: $blumiraUrl" -ForegroundColor Cyan
    } else {
        Write-Warning "Could not extract valid Blumira URL from the provided command string."
        Write-Warning "VM will be provisioned without installing the sensor."
    }
}

# --- Admin Check ---
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    Write-Warning "Please run this script as Administrator (Required for Hyper-V networking)."
    Break
}

function Check-HyperV {
    $isServer = (Get-CimInstance -ClassName Win32_OperatingSystem).ProductType -ne 1
    
    if ($isServer) {
        # Windows Server - use Get-WindowsFeature
        $feature = Get-WindowsFeature -Name Hyper-V
        if ($feature.Installed -ne $true) {
            Write-Warning "Hyper-V is not enabled."
            $confirm = Read-Host "Enable Hyper-V and restart now? (y/n)"
            if ($confirm -eq 'y') {
                Install-WindowsFeature -Name Hyper-V -IncludeManagementTools -Restart
            } else {
                Write-Error "Hyper-V is required."
                exit
            }
        }
    } else {
        # Windows 10/11 - use Get-WindowsOptionalFeature
        $feature = Get-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V-All -Online
        if ($feature.State -ne "Enabled") {
            Write-Warning "Hyper-V is not enabled."
            $confirm = Read-Host "Enable Hyper-V and restart now? (y/n)"
            if ($confirm -eq 'y') {
                Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All
                Restart-Computer
            } else {
                Write-Error "Hyper-V is required."
                exit
            }
        }
    }
}

function Install-VM {
    Check-HyperV
    Write-Host "=== Starting Hyper-V Setup ===" -ForegroundColor Cyan
    
    # 0. Pre-flight Cleanup (Crucial for releasing file locks)
    Write-Host "Ensuring previous VM instance is removed..." -ForegroundColor Cyan
    Stop-VM -Name $vmName -TurnOff -ErrorAction SilentlyContinue
    Remove-VM -Name $vmName -Force -ErrorAction SilentlyContinue
    Start-Sleep -Seconds 2

    # 1. Network Setup (NAT)
    Write-Host "Configuring NAT Network..." -ForegroundColor Cyan
    if (-not (Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue)) {
        New-VMSwitch -Name $switchName -SwitchType Internal | Out-Null
    }
    
    # FIX: Robustly identify the Host vNIC and force the Gateway IP
    $vNicNamePattern = "*$switchName*"
    $netAdapter = Get-NetAdapter | Where-Object { $_.Name -like $vNicNamePattern } | Select-Object -First 1
    
    if ($netAdapter) {
        # Force Network Category to Private (Fixes "Unidentified Network" firewall blocking NAT)
        Write-Host "  - Setting Network Category to Private..."
        Set-NetConnectionProfile -InterfaceIndex $netAdapter.ifIndex -NetworkCategory Private -ErrorAction SilentlyContinue

        $existingIPs = Get-NetIPAddress -InterfaceIndex $netAdapter.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue
        $gatewaySet = $false
        
        # Check if correct IP exists in the list
        if ($existingIPs) {
            foreach ($ip in $existingIPs) {
                if ($ip.IPAddress -eq $gatewayIP) { $gatewaySet = $true }
            }
        }

        if (-not $gatewaySet) {
             Write-Host "  - Enforcing Gateway IP $gatewayIP on Host Adapter ($($netAdapter.Name))..."
             # Clear old IPs to prevent conflicts
             Remove-NetIPAddress -InterfaceIndex $netAdapter.ifIndex -AddressFamily IPv4 -Confirm:$false -ErrorAction SilentlyContinue
             New-NetIPAddress -IPAddress $gatewayIP -PrefixLength 24 -InterfaceIndex $netAdapter.ifIndex -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
        } else {
             Write-Host "  - Host Gateway IP correct: $gatewayIP"
        }
    } else {
        Write-Warning "Could not find Host Virtual Adapter for '$switchName'. Networking may fail."
    }

    # Remove old NAT if exists
    Remove-NetNat -Name "${switchName}_NAT" -Confirm:$false -ErrorAction SilentlyContinue
    New-NetNat -Name "${switchName}_NAT" -InternalIPInterfaceAddressPrefix $natSubnet | Out-Null

    # 1.5 Host Firewall Rules (CRITICAL for Port Forwarding)
    Write-Host "Opening Host Firewall Ports (TCP/UDP 1514, TCP 8080, TCP 2222)..."
    $ruleName = "Blumira-Sensor-NAT-Access"
    Remove-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
    
    # Allow Syslog (TCP/UDP), Web (TCP), SSH (TCP) with EdgeTraversal allowed
    New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -LocalPort 1514 -Protocol TCP -Action Allow -Profile Any -EdgeTraversalPolicy Allow | Out-Null
    New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -LocalPort 1514 -Protocol UDP -Action Allow -Profile Any -EdgeTraversalPolicy Allow | Out-Null
    New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -LocalPort 8080 -Protocol TCP -Action Allow -Profile Any -EdgeTraversalPolicy Allow | Out-Null
    New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -LocalPort 2222 -Protocol TCP -Action Allow -Profile Any -EdgeTraversalPolicy Allow | Out-Null

    # Port Forwarding (Static Mappings)
    Write-Host "Setting Port Forwarding Rules..."
    $ports = @(
        @{Name="SSH"; Protocol="TCP"; External=2222; Internal=22},
        @{Name="SyslogTCP"; Protocol="TCP"; External=1514; Internal=514},
        @{Name="SyslogUDP"; Protocol="UDP"; External=1514; Internal=514},
        @{Name="SensorWeb"; Protocol="TCP"; External=8080; Internal=8080}
    )

    foreach ($p in $ports) {
        Get-NetNatStaticMapping -NatName "${switchName}_NAT" -ErrorAction SilentlyContinue | 
            Where-Object { $_.Protocol -eq $p.Protocol -and $_.ExternalPort -eq $p.External } | 
            Remove-NetNatStaticMapping -Confirm:$false -ErrorAction SilentlyContinue

        Add-NetNatStaticMapping -NatName "${switchName}_NAT" -Protocol $p.Protocol -ExternalIPAddress 0.0.0.0 -ExternalPort $p.External -InternalIPAddress $vmIP -InternalPort $p.Internal -ErrorAction SilentlyContinue | Out-Null
        
        Write-Host "  + Forwarding $($p.Protocol) $($p.External) -> ${vmIP}:$($p.Internal)"
    }

    # 2. Disk Preparation
    New-Item -ItemType Directory -Force -Path $baseDir | Out-Null
    
    # FIX: Hyper-V fails if folder is compressed
    Write-Host "Ensuring VM directory is uncompressed..."
    Start-Process -FilePath "compact.exe" -ArgumentList "/U", "/S", "/I", "`"$baseDir`"" -Wait -NoNewWindow

    if (-not (Test-Path $vmDisk)) {
        Write-Host "Preparing Disk Image..." -ForegroundColor Cyan
        
        if (-not (Test-Path "$baseDir\base.img")) {
            Write-Host "  - Downloading Ubuntu Cloud Image..."
            Start-BitsTransfer -Source $ubuntuImgUrl -Destination "$baseDir\base.img"
        }

        if (-not (Test-Path "$baseDir\qemu-img.exe")) {
            Write-Host "  - Fetching converter tool..."
            $installer = "$baseDir\qemu-install.exe"
            Start-BitsTransfer -Source $qemuToolsUrl -Destination $installer
            Start-Process -FilePath $installer -ArgumentList "/S", "/D=$baseDir\tools" -Wait
            Copy-Item "$baseDir\tools\qemu-img.exe" "$baseDir\qemu-img.exe"
            Copy-Item "$baseDir\tools\*.dll" "$baseDir\"
            Remove-Item "$baseDir\tools" -Recurse -Force
            Remove-Item $installer
        }

        Write-Host "  - Converting IMG to VHDX..."
        & "$baseDir\qemu-img.exe" convert -f qcow2 -O vhdx "$baseDir\base.img" $vmDisk
        
        Write-Host "  - Cleaning up converter tools..."
        Remove-Item "$baseDir\base.img", "$baseDir\qemu-img.exe", "$baseDir\*.dll" -ErrorAction SilentlyContinue
        
        # FIX: Decompress and Un-Sparse the VHDX
        Write-Host "  - Optimizing VHDX for Hyper-V..."
        Start-Process -FilePath "compact.exe" -ArgumentList "/U", "`"$vmDisk`"" -Wait -NoNewWindow
        fsutil sparse setflag "$vmDisk" 0 | Out-Null

        Resize-VHD -Path $vmDisk -SizeBytes 40GB
    }

    # 3. Cloud-Init (Seed Disk)
    Write-Host "Generating Configuration..." -ForegroundColor Cyan
    $randId = Get-Random
    
    # Extract Sensor ID from URL if provided
    $sensorId = "blumira" # Default placeholder
    if ($blumiraUrl) {
        $match = [regex]::Match($blumiraUrl, 'installer_([a-f0-9\-]+)')
        if ($match.Success) {
            $sensorId = $match.Groups[1].Value
        }
    }

    $userData = @"
#cloud-config
hostname: $vmName
ssh_pwauth: True

users:
  - name: $username
    lock_passwd: false
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash

chpasswd:
  list: |
    ${username}:${password}
  expire: False

packages:
  - netcat
  - curl
  # docker.io handled in script

write_files:
  - path: /root/install_blumira.sh
    permissions: '0700'
    content: |
      #!/bin/bash
      LOG=/var/log/blumira_setup.log
      # Use backtick to escape Bash variables
      echo "--- Starting Blumira Setup Script at `$(date) ---" >> `$LOG
      
      # 0. NETWORK ROUTING FIXES
      echo "Configuring Router Mode..." >> `$LOG
      sysctl -w net.ipv4.ip_forward=1 >> `$LOG
      echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
      ufw disable >> `$LOG 2>&1
      iptables -P FORWARD ACCEPT
      iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
      
      mkdir -p /etc/iptables
      iptables-save > /etc/iptables/rules.v4
      echo "pre-up iptables-restore < /etc/iptables/rules.v4" >> /etc/network/interfaces

      modprobe overlay
      modprobe br_netfilter
      echo "overlay" >> /etc/modules
      echo "br_netfilter" >> /etc/modules

      # 1. FIX: Kernel Parameters for Legacy Docker
      if ! grep -q "swapaccount=1" /proc/cmdline; then
        if [ -f /etc/blumira_cgroup_attempted ]; then
             echo "CRITICAL: Kernel fix attempted but not active. Preventing loop." >> `$LOG
        else
             echo "Updating Kernel Parameters (Cgroup v1 + Memory/Swap)..." >> `$LOG
             touch /etc/blumira_cgroup_attempted
             
             cat <<EOF > /etc/default/grub.d/99-blumira-settings.cfg
      GRUB_CMDLINE_LINUX_DEFAULT="\$GRUB_CMDLINE_LINUX_DEFAULT cgroup_enable=memory swapaccount=1 systemd.unified_cgroup_hierarchy=0"
      EOF
             
             update-grub >> `$LOG 2>&1
             
             cat <<EOF > /etc/systemd/system/blumira-resume.service
      [Unit]
      Description=Resume Blumira Installation
      After=network-online.target
      Wants=network-online.target
      [Service]
      Type=oneshot
      ExecStart=/bin/bash /root/install_blumira.sh
      RemainAfterExit=true
      [Install]
      WantedBy=multi-user.target
      EOF
             
             systemctl enable blumira-resume.service
             echo "Rebooting..." >> `$LOG
             reboot
             exit 0
        fi
      fi
      
      # 2. Resume after reboot
      echo "Environment compatible. Proceeding..." >> `$LOG
      
      echo "Waiting for internet..." >> `$LOG
      TIMEOUT=60
      while [ `$TIMEOUT -gt 0 ]; do
        if ping -c1 8.8.8.8 &>/dev/null; then
            echo "Network is UP." >> `$LOG
            break
        fi
        sleep 1
        let TIMEOUT=TIMEOUT-1
      done

      # FIX: Install Docker explicitly AFTER network is confirmed
      if ! command -v docker &> /dev/null; then
          echo "Docker not found. Installing..." >> `$LOG
          sleep 5
          apt-get update >> `$LOG 2>&1
          apt-get install -y docker.io >> `$LOG 2>&1
      fi

      echo "Waiting for Docker Daemon..." >> `$LOG
      until [ -S /var/run/docker.sock ]; do sleep 1; done
      echo "Docker is UP." >> `$LOG

      if systemctl is-enabled blumira-resume.service &>/dev/null; then
        systemctl disable blumira-resume.service
        rm /etc/systemd/system/blumira-resume.service
      fi
      
      # 3. Install Sensor
      SENSOR_URL="$blumiraUrl"
      
      if [ -z "`$SENSOR_URL" ]; then
         echo "No Sensor URL provided via arguments. Skipping Sensor installation." >> `$LOG
         echo "Docker and Network are ready for manual install." >> `$LOG
         exit 0
      fi

      if [ -f /etc/blumira_installed ]; then
        echo "Already installed (Flag detected)." >> `$LOG
        exit 0
      fi

      if docker ps -a | grep -q "$sensorId"; then
         echo "Sensor container ($sensorId) already exists. Marking complete." >> `$LOG
         touch /etc/blumira_installed
         exit 0
      fi
      
      echo "Downloading Installer..." >> `$LOG
      MAX_RETRIES=5
      COUNT=0
      while [ `$COUNT -lt `$MAX_RETRIES ]; do
        curl -L "`$SENSOR_URL" -o /tmp/blumira_install.sh && break
        ((COUNT++))
        echo "Download failed. Retrying (`$COUNT/`$MAX_RETRIES)..." >> `$LOG
        sleep 5
      done

      if [ ! -f /tmp/blumira_install.sh ]; then
         echo "FATAL: Could not download installer." >> `$LOG
         exit 1
      fi

      chmod +x /tmp/blumira_install.sh
      
      echo "Running Installer..." >> `$LOG
      /bin/bash /tmp/blumira_install.sh >> `$LOG 2>&1
      
      if [ `$? -eq 0 ]; then
        touch /etc/blumira_installed
        echo "SUCCESS." >> `$LOG
      else
        echo "FAILED." >> `$LOG
      fi

runcmd:
  - /bin/bash /root/install_blumira.sh
"@
    # Separate Network Config for NoCloud
    $networkConfig = @"
version: 2
ethernets:
  id0:
    match:
      name: "e*"
    addresses:
      - $vmIP/24
    gateway4: $gatewayIP
    nameservers:
      addresses: [8.8.8.8, 8.8.4.4]
"@

    $userData | Out-File -FilePath "$baseDir\user-data" -Encoding ASCII -NoNewline
    $metaData | Out-File -FilePath "$baseDir\meta-data" -Encoding ASCII -NoNewline
    $networkConfig | Out-File -FilePath "$baseDir\network-config" -Encoding ASCII -NoNewline

    # Create Seed VHDX
    Write-Host "Creating Config Disk..."
    if (Test-Path $seedDisk) { Remove-Item $seedDisk -Force }
    
    $driveLetter = "Z"
    $dpScript = @"
create vdisk file="$seedDisk" maximum=100 type=fixed
select vdisk file="$seedDisk"
attach vdisk
create partition primary
select partition 1
format fs=fat32 label="cidata" quick
assign letter=$driveLetter
"@
    $dpScript | Out-File "$baseDir\dp_create.txt" -Encoding ASCII
    & diskpart /s "$baseDir\dp_create.txt" | Out-Null
    
    Start-Sleep -Seconds 2
    if (Test-Path "${driveLetter}:") {
        Copy-Item "$baseDir\user-data" "${driveLetter}:\user-data"
        Copy-Item "$baseDir\meta-data" "${driveLetter}:\meta-data"
        Copy-Item "$baseDir\network-config" "${driveLetter}:\network-config"
        
        $dpDetach = "select vdisk file=`"$seedDisk`"`ndetach vdisk"
        $dpDetach | Out-File "$baseDir\dp_detach.txt" -Encoding ASCII
        & diskpart /s "$baseDir\dp_detach.txt" | Out-Null
        
        Remove-Item "$baseDir\dp_*.txt", "$baseDir\user-data", "$baseDir\meta-data", "$baseDir\network-config"
    } else {
        Write-Error "Failed to mount seed disk."
        return
    }

    # 4. Create and Start VM
    Write-Host "Creating Virtual Machine..." -ForegroundColor Cyan
    Remove-VM -Name $vmName -Force -ErrorAction SilentlyContinue
    
    New-VM -Name $vmName -MemoryStartupBytes 2GB -Generation 2 -VHDPath $vmDisk -SwitchName $switchName | Out-Null
    
    Set-VMProcessor -VMName $vmName -Count 2 -ExposeVirtualizationExtensions $true
    Set-VMFirmware -VMName $vmName -EnableSecureBoot Off -FirstBootDevice $(Get-VMDvdDrive -VMName $vmName)
    Add-VMHardDiskDrive -VMName $vmName -Path $seedDisk -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 1
    
    Start-VM -Name $vmName
    Write-Host "VM Started! It will auto-reboot once to fix Kernel configs." -ForegroundColor Green
    Write-Host "Wait ~60 seconds before attempting SSH."
}

function Uninstall-VM {
    Write-Host "Stopping VM..."
    Stop-VM -Name $vmName -TurnOff -ErrorAction SilentlyContinue
    Remove-VM -Name $vmName -Force -ErrorAction SilentlyContinue
    
    Write-Host "Removing NAT Network..."
    Remove-NetNat -Name "${switchName}_NAT" -Confirm:$false -ErrorAction SilentlyContinue
    Remove-VMSwitch -Name $switchName -Force -ErrorAction SilentlyContinue
    
    Write-Host "Removing Firewall Rules..."
    Remove-NetFirewallRule -DisplayName "Blumira-Sensor-NAT-Access" -ErrorAction SilentlyContinue
    
    Write-Host "Removing Files..."
    Remove-Item $baseDir -Recurse -Force -ErrorAction SilentlyContinue
    Write-Host "Done." -ForegroundColor Green
}

function Status-VM {
    $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
    if ($vm) {
        Write-Host "VM State: $($vm.State)" -ForegroundColor Cyan
        Write-Host "Uptime:   $($vm.Uptime)"
        Write-Host "IP (Int): $vmIP"
        $sshTest = Test-NetConnection -ComputerName $vmIP -Port 22 -WarningAction SilentlyContinue
        if ($sshTest.TcpTestSucceeded) { Write-Host "SSH:      Accessible (Port 22)" -ForegroundColor Green }
        else { Write-Host "SSH:      Not responding" -ForegroundColor Yellow }
    } else {
        Write-Host "VM Not Found." -ForegroundColor Red
    }
}

function Auth-VM {
    if (Get-Command ssh-keygen -ErrorAction SilentlyContinue) {
        ssh-keygen -R "[localhost]:2222" 2>$null | Out-Null
        ssh-keygen -R "$vmIP" 2>$null | Out-Null
    }
    Write-Host "Connecting to $vmName..."
    Write-Host "Password: $password" -ForegroundColor Gray
    ssh $username@$vmIP
}

switch ($Mode) {
    "Install"   { Install-VM }
    "Uninstall" { Uninstall-VM }
    "Status"    { Status-VM }
    "Auth"      { Auth-VM }
    "Start"     { Start-VM -Name $vmName }
    "Stop"      { Stop-VM -Name $vmName }
}

Deploying a headless Ubuntu VM with Blumira sensor

To deploy a headless Ubuntu VM and install the Blumira sensor on it, do the following: 

  1. Obtain your sensor installation command from Blumira by doing the following:
    1. In the app, navigate to Sensors > Add Sensor.
    2. Copy the Linux installation command to use with the PowerShell script.
  2. Install the Manage-blumira.ps1 script:
    1. On your Windows machine, click Start.
    2. Type PowerShell into the search bar.
    3. Right-click Windows PowerShell and click Run as Administrator.
    4. On the User Account Control (UAC) prompt, click Yes.
    5. Paste the following command, including the sensor installation command that you copied in Blumira:
.\Manage-blumira.ps1 -Mode Install -SensorCommand "PASTE_YOUR_BLUMIRA_COMMAND_HERE"

Managing the VM

After installing the headless Ubuntu VM, you can use the following commands to manage it:

PurposeCommandDetails
Check status.\Manage-blumira.ps1 -Mode StatusVerify the VM state, IP address, and SSH connectivity.
SSH access.\Manage-blumira.ps1 -Mode AuthThis command logs into the VM automatically, manages the password, and clears stale host keys
Uninstall or reset.\Manage-blumira.ps1 -Mode UninstallCaution: This command deletes the VM, the disk, the firewall rules, and the NAT network. Use this to start over.
Start.\Manage-blumira.ps1 -Mode StartManually control the VM’s power state with these commands.
Stop.\Manage-blumira.ps1 -Mode Stop

Troubleshooting

If the sensor is not showing up in Blumira, do the following: 

  1. Wait at least 5 minutes. The installation involves a reboot and a large Docker download.
  2. Use the SSH Access command to get into the VM: .\Manage-blumira.ps1 -Mode Auth
  3. Check the installation log:
    1. View the setup history by running the following command: cat /var/log/blumira_setup.log 
    2. Scroll to the bottom of the output, and look for the word "SUCCESS." If you see "FAILED" or "FATAL: Could not download installer,” the VM might not have had internet access during the initial boot.
  4. Check running containers:
    1. View active Docker containers by running the following command: sudo docker ps
    2. Look for a container listed (usually containing your unique sensor ID) with a status of "Up." If the list is completely empty, or if the container says "Exited," the sensor crashed or failed to install.

If you receive the specific error messages “SSH: Connection Refused” or “Not Responding,” the VM is likely rebooting to apply the Cgroup v1 fix. Wait 60 seconds and try again.

If you receive the error message “Script failed to resize disk,” that means your drive is compressed.  The script attempts to auto-fix this, but you might need to manually decompress the C:\VMs folder properties.