Files
Tanium/API/rest - list sensor detail.ps1
2025-10-31 08:59:02 +01:00

239 lines
9.1 KiB
PowerShell

<#
.SYNOPSIS
Export or print the scripts used by Sensors per OS (Windows/Mac/Linux/AIX/Solaris…), via Tanium REST.
Reads Url/Token from config.json. Can dump files (-DumpPath) and/or print scripts to console (-Print).
.USAGE
# Index only (table)
.\Export-SensorScriptsByOS.ps1
# Dump all scripts to files + show a grid
.\Export-SensorScriptsByOS.ps1 -DumpPath .\out -Grid
# Only Windows scripts of sensors matching a name regex, and print to console
.\Export-SensorScriptsByOS.ps1 -NameLike '^WSUS|BIOS' -Os Windows -Print
# One sensor by id, dump to folder and print
.\Export-SensorScriptsByOS.ps1 -Id 665 -DumpPath C:\Temp\sensor_src -Print
# Export index to CSV
.\Export-SensorScriptsByOS.ps1 -DumpPath .\out -ExportCsv .\index.csv
#>
param(
[string]$Url,
[string]$Token,
[switch]$SkipCertCheck,
[string]$NameLike,
[int]$Id,
[string[]]$Os, # ex: -Os Windows,Mac
[switch]$Print, # print script text in console
[string]$DumpPath, # write each script to a file
[string]$ExportCsv, # write index CSV
[switch]$Grid,
[switch]$Raw
)
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
# ---------- Helpers ----------
function New-BaseUri([string]$HostLike) {
$h = $HostLike.Trim()
if ($h -match '^https?://') { $h = $h -replace '^https?://','' }
$h = $h.TrimEnd('/')
if ([string]::IsNullOrWhiteSpace($h)) { throw 'TaniumUrl empty after normalization.' }
"https://$h"
}
# TLS bypass for Windows PowerShell 5.1
$script:__oldCb = $null
function Enter-InsecureTls { param([switch]$Enable)
if (-not $Enable) { return }
$script:__oldCb = [System.Net.ServicePointManager]::ServerCertificateValidationCallback
$cb = [System.Net.Security.RemoteCertificateValidationCallback]{ param($s,$c,$ch,$e) $true }
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = $cb
}
function Exit-InsecureTls {
if ($script:__oldCb -ne $null) {
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = $script:__oldCb
$script:__oldCb = $null
}
}
# ---------- Load config ----------
$BaseDir = if ($PSScriptRoot) { $PSScriptRoot } else { $pwd.Path }
$configPath = Join-Path $BaseDir 'config.json'
if (-not $Url -or -not $Token) {
if (-not (Test-Path $configPath)) { throw "Missing -Url/-Token and config.json not found: $configPath" }
$cfg = Get-Content -Path $configPath -Raw | ConvertFrom-Json
if (-not $Url) { $Url = [string]$cfg.TaniumUrl }
if (-not $Token) { $Token = [string]$cfg.TaniumApiToken }
if (-not $PSBoundParameters.ContainsKey('SkipCertCheck')) { $SkipCertCheck = [bool]$cfg.SkipCertificateCheck }
}
$Base = New-BaseUri $Url
$Headers = @{ session = $Token; 'Content-Type' = 'application/json' }
function Invoke-Tanium([string]$Method,[string]$Path,[object]$BodyObj,[switch]$AllowError) {
$uri = if ($Path -match '^https?://') { $Path } else { "$Base$Path" }
$body = if ($null -ne $BodyObj) { $BodyObj | ConvertTo-Json -Depth 12 } else { $null }
$ps7 = ($PSVersionTable.PSVersion.Major -ge 7)
try {
if ($ps7 -and $SkipCertCheck) {
return Invoke-RestMethod -SkipCertificateCheck -Method $Method -Uri $uri -Headers $Headers -Body $body
} else {
Enter-InsecureTls -Enable:$SkipCertCheck
return Invoke-RestMethod -Method $Method -Uri $uri -Headers $Headers -Body $body
}
}
catch { if ($AllowError) { return $null } else { throw } }
finally { Exit-InsecureTls }
}
function TryGet($obj, [string[]]$names) {
foreach ($n in $names) {
$p = $obj.PSObject.Properties[$n]
if ($p -and $p.Value) { return $p.Value }
}
return $null
}
function CleanName([string]$s) { if ($s) { ($s -replace '[\\/:*?"<>|]','_').Trim() } }
function Detect-OS([string]$raw) {
if (-not $raw) { return $null }
$r = $raw.ToLowerInvariant()
if ($r -match 'win') { return 'Windows' }
if ($r -match 'mac|darwin|osx'){ return 'Mac' }
if ($r -match 'lin|posix|unix'){ return 'Linux' }
# laissons tels quels pour AIX/Solaris/HP-UX/etc.
return ($raw -replace '\s+',' ').Trim()
}
# Per-OS extraction from a detailed sensor payload
function Get-SourcesByOS([object]$sensorDetail) {
$map = @{}
# 1) flat fields
$ws = TryGet $sensorDetail @('windows_source','windowsQuery','windows_query')
$ls = TryGet $sensorDetail @('linux_source','posix_source','linuxQuery','linux_query','posixQuery','posix_query','solaris_source','aix_source')
$ms = TryGet $sensorDetail @('mac_source','darwin_source','macQuery','mac_query')
$generic = TryGet $sensorDetail @('source','query_text','script','definition','definition_text','content')
if ($ws) { $map['Windows'] = [string]$ws }
if ($ms) { $map['Mac'] = [string]$ms }
if ($ls) { $map['Linux'] = [string]$ls } # certaines versions renvoient aussi AIX/Solaris en champs dédiés : traités ci-dessous
# 2) nested arrays, avec platform/operating_system + source/text/...
foreach ($arrName in @('queries','sources','os_queries','platforms')) {
$arr = $sensorDetail.PSObject.Properties[$arrName].Value
if ($arr -and ($arr -is [System.Collections.IEnumerable])) {
foreach ($q in $arr) {
$plat = TryGet $q @('platform','operating_system','os','name','type')
$code = TryGet $q @('source','text','query_text','script','definition','content','body')
if ($code) {
$os = Detect-OS ([string]$plat)
if (-not $os) { $os = 'All' }
# Ne pas écraser si déjà présent
if (-not $map.ContainsKey($os)) { $map[$os] = [string]$code }
}
}
}
}
if ($map.Count -eq 0 -and $generic) { $map['All'] = [string]$generic }
return $map
}
# ---------- Get sensor list (or just one) ----------
$items = $null
if ($Id) {
$d = Invoke-Tanium GET "/api/v2/sensors/$Id" $null -AllowError
if ($Raw) { $d | ConvertTo-Json -Depth 14; return }
$list = Invoke-Tanium GET "/api/v2/sensors" $null
$items = if ($list.data) { $list.data } elseif ($list.items) { $list.items } else { $list }
$items = $items | Where-Object { $_.id -eq $Id }
} else {
$list = Invoke-Tanium GET "/api/v2/sensors" $null
$items = if ($list.data) { $list.data } elseif ($list.items) { $list.items } else { $list }
}
if (-not $items) { Write-Output 'No sensors visible via API.'; return }
if ($NameLike) { $items = $items | Where-Object { $_.name -match $NameLike } }
if (-not $items) { Write-Output 'No matching sensors.'; return }
# Prepare dump folder if requested
if ($DumpPath) {
if (-not (Test-Path -LiteralPath $DumpPath)) { New-Item -ItemType Directory -Path $DumpPath | Out-Null }
}
# Normalize OS filter to case-insensitive hashset
$osFilter = $null
if ($Os -and $Os.Count -gt 0) {
$osFilter = New-Object System.Collections.Generic.HashSet[string] ([StringComparer]::OrdinalIgnoreCase)
foreach ($o in $Os) { if ($o) { [void]$osFilter.Add($o) } }
}
$rows = New-Object System.Collections.Generic.List[object]
foreach ($s in $items) {
$sid = $s.id; $sname = [string]$s.name
$detail = Invoke-Tanium GET "/api/v2/sensors/$sid" $null -AllowError
# unwrap common envelopes
$sensorDetail = $null
foreach ($k in 'data','sensor','item') { if ($null -eq $sensorDetail -and $detail.PSObject.Properties[$k]) { $sensorDetail = $detail.$k } }
if (-not $sensorDetail) { if ($detail.items -and $detail.items.Count -gt 0) { $sensorDetail = $detail.items[0] } else { $sensorDetail = $detail } }
$sources = Get-SourcesByOS $sensorDetail
if ($sources.Keys.Count -eq 0) {
$rows.Add([pscustomobject]@{ Id=$sid; Name=$sname; OS='—'; HasScript=$false; Length=0; Path=''; Text=$null })
continue
}
# apply OS filter if any
$osKeys = $sources.Keys
if ($osFilter) { $osKeys = $osKeys | Where-Object { $osFilter.Contains($_) } }
foreach ($os in $osKeys) {
$txt = [string]$sources[$os]
$len = ($txt | Measure-Object -Character).Characters
$outPath = ''
if ($DumpPath) {
$fn = (CleanName($sname) + "__" + ($os -replace '\s+','_') + ".txt")
$outPath = Join-Path $DumpPath $fn
Set-Content -LiteralPath $outPath -Value $txt -Encoding UTF8
}
$rows.Add([pscustomobject]@{ Id=$sid; Name=$sname; OS=$os; HasScript=$true; Length=$len; Path=$outPath; Text=$txt })
}
}
$rows = $rows | Sort-Object Name, OS
# Optional CSV of the index (no full text)
if ($ExportCsv) {
try {
$rows | Select-Object Id,Name,OS,HasScript,Length,Path | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $ExportCsv
Write-Host "Index exported to: $ExportCsv" -ForegroundColor Green
} catch { Write-Warning "CSV export failed: $($_.Exception.Message)" }
}
# Grid
if ($Grid) {
if (Get-Command Out-GridView -ErrorAction SilentlyContinue) {
$rows | Select-Object Id,Name,OS,HasScript,Length,Path | Out-GridView -Title ("Sensor Scripts by OS — {0} rows" -f $rows.Count)
} else {
Write-Warning "Out-GridView not available. On PS7+: Install-Module Microsoft.PowerShell.GraphicalTools"
}
}
# Table
$rows | Select-Object Id,Name,OS,HasScript,Length,Path | Format-Table -AutoSize
# Print scripts to console
if ($Print) {
foreach ($r in $rows) {
if (-not $r.HasScript -or -not $r.Text) { continue }
Write-Host "`n===== SENSOR: $($r.Name) | OS: $($r.OS) | Id: $($r.Id) =====" -ForegroundColor Cyan
$r.Text
}
}