239 lines
9.1 KiB
PowerShell
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
|
|
}
|
|
}
|