<# .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 } }