<# .SYNOPSIS List Tanium **Sensors** via the REST API, using Url/Token from `config.json` by default. Shows: Id, Name, Content Set, Category, Parameters?, CacheSeconds (if exposed). Supports filters by name/content set/category, Out‑GridView, and CSV export. .USAGE # Basic (reads config.json in the same folder) ./List-Sensors.ps1 # Filter by name/content set/category (regex), show grid and export CSV ./List-Sensors.ps1 -NameLike '^BIOS' -ContentSetLike 'Deploy' -CategoryLike 'Hardware' -Grid -ExportCsv C:\Temp\sensors.csv # Raw JSON ./List-Sensors.ps1 -Raw # Override Url/Token explicitly ./List-Sensors.ps1 -Url tanium.pp.dktinfra.io -Token 'token-xxxx' #> param( [string]$Url, [string]$Token, [switch]$SkipCertCheck, [string]$NameLike, [string]$ContentSetLike, [string]$CategoryLike, [switch]$Raw, [switch]$Grid, [string]$ExportCsv ) $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 if needed ---------- $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) { $uri = if ($Path -match '^https?://') { $Path } else { "$Base$Path" } $body = if ($null -ne $BodyObj) { $BodyObj | ConvertTo-Json -Depth 10 } else { $null } $ps7 = ($PSVersionTable.PSVersion.Major -ge 7) if ($ps7 -and $SkipCertCheck) { return Invoke-RestMethod -SkipCertificateCheck -Method $Method -Uri $uri -Headers $Headers -Body $body } else { try { Enter-InsecureTls -Enable:$SkipCertCheck; return Invoke-RestMethod -Method $Method -Uri $uri -Headers $Headers -Body $body } finally { Exit-InsecureTls } } } # ---------- Fetch Sensors + Content Sets (for names) ---------- $resp = Invoke-Tanium GET "/api/v2/sensors" $null $sensors = if ($resp.data) { $resp.data } elseif ($resp.items) { $resp.items } else { $resp } $csResp = Invoke-Tanium GET "/api/v2/content_sets" $null $contentSets = if ($csResp.data) { $csResp.data } elseif ($csResp.items) { $csResp.items } else { $csResp } $csById = @{} foreach ($cs in $contentSets) { if ($cs.id -ne $null) { $csById[$cs.id] = $cs } } if ($Raw) { [pscustomobject]@{ sensors = $sensors; contentSets = $contentSets } | ConvertTo-Json -Depth 14 return } if (-not $sensors) { Write-Output 'No sensors returned.'; return } # ---------- Helpers to read maybe-present fields ---------- function TryField($obj, $path) { try { $val = $obj foreach ($p in ($path -split '\.')) { if ($null -eq $val) { return $null } $pp = $val.PSObject.Properties[$p] if (-not $pp) { return $null } $val = $pp.Value } return $val } catch { return $null } } # ---------- Shape output ---------- $rows = foreach ($s in $sensors) { $name = TryField $s 'name' if ($NameLike -and $name -and -not ($name -match $NameLike)) { continue } # content set: could be nested or just id $csName = '' $csId = TryField $s 'content_set.id' if ($null -eq $csId) { $csId = TryField $s 'content_set_id' } if ($null -ne $csId -and $csById.ContainsKey($csId)) { $csName = $csById[$csId].name } if ($ContentSetLike -and $csName -and -not ($csName -match $ContentSetLike)) { continue } $category = TryField $s 'category' if ($CategoryLike -and $category -and -not ($category -match $CategoryLike)) { continue } $hasParams = $false if (TryField $s 'parameters') { $hasParams = $true } elseif (TryField $s 'parameter_definitions') { $hasParams = $true } elseif (TryField $s 'parameterDefinitions') { $hasParams = $true } $cacheSec = TryField $s 'expiration_seconds' if ($null -eq $cacheSec) { $cacheSec = TryField $s 'cache_seconds' } [pscustomobject]@{ Id = TryField $s 'id' Name = $name ContentSet = $csName Category = $category Parameters = $hasParams CacheSeconds = $cacheSec } } if (-not $rows) { Write-Output 'No matching sensors.'; return } $rowsSorted = $rows | Sort-Object Name if ($ExportCsv) { try { $rowsSorted | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $ExportCsv; Write-Host "Exported to: $ExportCsv" -ForegroundColor Green } catch { Write-Warning "CSV export failed: $($_.Exception.Message)" } } if ($Grid) { $title = "Sensors — {0} item(s)" -f ($rowsSorted.Count) if (Get-Command Out-GridView -ErrorAction SilentlyContinue) { $rowsSorted | Out-GridView -Title $title; return } else { Write-Warning "Out-GridView not available. On PS7+: Install-Module Microsoft.PowerShell.GraphicalTools" } } $rowsSorted | Format-Table -AutoSize