Lightning-Fast Tanium Deployment Audits with PowerShell
If you manage a large-scale enterprise environment, you already know deploying software is only half the battle. The real work is verifying that the deployment actually succeeded across hundreds of endpoints.
While the Tanium console is incredibly powerful for deploying Out-of-Band (OOB) software packages via the Tanium Deploy module, sometimes you need to quickly scrape the raw deployment logs from the endpoints themselves—especially when troubleshooting stubborn failures or generating targeted compliance reports for your management team.
I’m sharing this PowerShell script I have used successfully which I've found useful in enterprise environments to automate this exact process.
# ==============================================================================
# Configuration
# ==============================================================================
# 1. Target a Specific System (Overrides Group settings if populated)
# Example: "app-server-01.domain.local"
$TargetSystem = ""
# 2. Target a Specific Group (Used if $TargetSystem is blank)
# Options: "ALL", "GROUP A", "GROUP B", "GROUP C"
$TargetGroup = "GROUP A"
# 3. Filter by Deployment ID (Leave blank "" to return ALL OOB deployments)
# Example: "12345"
$TargetDeploymentID = ""
# 4. Export Successful Responses to CSV (Leave blank "" to skip)
$ExportCsvPath = "C:\Temp\OOB_DeployStatus_Success.csv"
# 5. Export Unreachable Systems to CSV (Leave blank "" to skip)
$ExportFailuresCsvPath = "C:\Temp\OOB_DeployStatus_Failed.csv"
# ==============================================================================
# Server Definitions
# ==============================================================================
$AllSystems = [ordered]@{
"GROUP A" = @(
"DC-01.domain.local", "DC-02.domain.local", "FILE-SRV-01.domain.local",
"WEB-SRV-01.domain.local", "WEB-SRV-02.domain.local"
)
"GROUP B" = @(
"APP-SRV-01.domain.local", "APP-SRV-02.domain.local", "DB-SRV-01.domain.local",
"PROXY-01.domain.local", "PROXY-02.domain.local"
)
"GROUP C" = @(
"TEST-SRV-01.domain.local", "TEST-SRV-02.domain.local", "DEV-DB-01.domain.local"
)
}
# ==============================================================================
# Build the Target List
# ==============================================================================
$TargetComputers = @()
if (![string]::IsNullOrWhiteSpace($TargetSystem)) {
$TargetComputers += $TargetSystem
Write-Host "Targeting specific system: $TargetSystem" -ForegroundColor Cyan
}
elseif ($TargetGroup -ne "ALL") {
if ($AllSystems.Contains($TargetGroup)) {
$TargetComputers += $AllSystems[$TargetGroup]
Write-Host "Targeting Group: $TargetGroup ($($TargetComputers.Count) systems)" -ForegroundColor Cyan
}
else {
Write-Warning "The group '$TargetGroup' was not found. Please verify the name."
exit
}
}
else {
foreach ($Group in $AllSystems.Values) {
$TargetComputers += $Group
}
Write-Host "Targeting ALL groups ($($TargetComputers.Count) systems total)" -ForegroundColor Cyan
}
# ==============================================================================
# Execution Block (Parallel)
# ==============================================================================
Write-Host "Initiating parallel queries to Tanium clients... This may take a moment.`n" -ForegroundColor Yellow
$Results = Invoke-Command -ComputerName $TargetComputers -ArgumentList $TargetDeploymentID -ErrorAction SilentlyContinue -ScriptBlock {
param($TargetID)
# Path for Tanium 'Deploy' module deployments (Out of band)
$FilePath = "C:\Program Files (x86)\Tanium\Tanium Client\Tools\SoftwareManagement\data\preprocessed\deploy-deployments.txt"
if (!(Test-Path $FilePath)) {
return [PSCustomObject]@{
Status = "Missing File"
Details = "Deploy module status file not found on this system."
}
}
# Grab the raw content of the deploy status file
$DeployData = Get-Content $FilePath
# If a specific Target ID was provided, filter the text lines
if (![string]::IsNullOrWhiteSpace($TargetID)) {
# Match lines that contain the target ID
$DeployData = $DeployData | Where-Object { $_ -match $TargetID }
if (!$DeployData) {
return [PSCustomObject]@{
Status = "Not Found"
Details = "Deployment ID '$TargetID' was not found in the status log."
}
}
}
return [PSCustomObject]@{
Status = "Success"
Details = $DeployData -join " | " # Joins array of strings so it displays nicely if multiple lines exist
}
}
# ==============================================================================
# Process and Output Results
# ==============================================================================
if ($Results) {
$FormattedResults = $Results | Select-Object PSComputerName, Status, Details
Write-Host "--- SUCCESSFUL RESPONSES ---" -ForegroundColor Green
$FormattedResults | Format-Table -AutoSize
if (![string]::IsNullOrWhiteSpace($ExportCsvPath)) {
try {
$ExportDir = Split-Path $ExportCsvPath
if (![string]::IsNullOrWhiteSpace($ExportDir) -and !(Test-Path $ExportDir)) { New-Item -ItemType Directory -Force -Path $ExportDir | Out-Null }
$FormattedResults | Export-Csv -Path $ExportCsvPath -NoTypeInformation -Force
Write-Host "Successfully exported connected results to: $ExportCsvPath" -ForegroundColor Green
}
catch { Write-Warning "Failed to export successes to CSV. Error: $($_.Exception.Message)" }
}
}
else {
Write-Host "No deployment statuses were returned from any system." -ForegroundColor Yellow
}
# ==============================================================================
# Connection Failures
# ==============================================================================
$RespondedComputers = @()
if ($Results) {
$RespondedComputers = $Results | Select-Object -ExpandProperty PSComputerName -Unique | ForEach-Object { $_.ToLower() }
}
$UnreachableComputers = $TargetComputers | Where-Object { $_.ToLower() -notin $RespondedComputers }
if ($UnreachableComputers.Count -gt 0) {
Write-Host "`n--- UNREACHABLE SYSTEMS ($($UnreachableComputers.Count)) ---" -ForegroundColor Red
$FailedObjects = $UnreachableComputers | ForEach-Object {
[PSCustomObject]@{
ComputerName = $_
Status = "Connection Failed"
Details = "System offline, DNS failure, or WinRM blocked."
}
}
$FailedObjects | Format-Table -AutoSize
if (![string]::IsNullOrWhiteSpace($ExportFailuresCsvPath)) {
try {
$ExportDir = Split-Path $ExportFailuresCsvPath
if (![string]::IsNullOrWhiteSpace($ExportDir) -and !(Test-Path $ExportDir)) { New-Item -ItemType Directory -Force -Path $ExportDir | Out-Null }
$FailedObjects | Export-Csv -Path $ExportFailuresCsvPath -NoTypeInformation -Force
Write-Host "Successfully exported failed systems to: $ExportFailuresCsvPath" -ForegroundColor Red
}
catch { Write-Warning "Failed to export failures to CSV. Error: $($_.Exception.Message)" }
}
}
The Challenge: Finding the Logs
When using the standard Tanium Patch module, statuses are usually kept in neat XML files. However, the Tanium Deploy module handles software packages differently. The deployment statuses are stored in a preprocessed text file located here on the endpoint: C:\Program Files (x86)\Tanium\Tanium Client\Tools\SoftwareManagement\data\preprocessed\deploy-deployments.txt
Querying this file manually server-by-server using a standard foreach loop takes far too long. If a single server is offline, your script hangs waiting for a timeout.
The Solution: Parallel Execution
To solve this, we can take advantage of the fact that PowerShell's Invoke-Command cmdlet natively supports parallel execution. By feeding it an array of computer names, PowerShell will cast a wide net, executing the script block on all target systems simultaneously.
Targeting Flexibility
You can target a single system, predefined groups (e.g., Group A, Group B), or query your entire fleet at once.
Targeted ID Filtering
Looking for a specific software package? Pass the Deployment ID into the parameters, and the script will filter the endpoint's log to return only the lines matching that exact deployment.
Automated CSV Exports
The script automatically formats the raw text data and dumps successful queries into one CSV file, making it easy to slice and dice in Excel.
Failure Tracking
Silent failures are a SysAdmin's worst nightmare. The script cross-references the list of servers we attempted to query against the ones that actually responded. Any server that failed (due to DNS issues, offline status, or WinRM blocks) is corralled into a dedicated "Unreachable Systems" CSV file so you can hand it off to your infrastructure team.
Wrapping Up
By leveraging parallel processing and handling connection failures gracefully, this script turns a tedious 30-minute manual audit into a task that takes just a few seconds.
Whether you are pushing emergency zero-day mitigations or rolling out new enterprise applications, having quick, programmatic access to your endpoint statuses is a game-changer.
Have you found a different way to audit your Tanium deployments?
Did you enjoy CarlsCloud™ today and did I help you at all?
If so, buy me a coffee or just shoot me a note via LinkedIn to say thanks it would mean a lot!