<# .SYNOPSIS Query Tanium Gateway (GraphQL) for endpoints and return Computer Name (NetBIOS), Model, and BIOS Version. Supports pagination, optional filtering from a simple list file (one name per line), Out-GridView, and CSV export. .DESCRIPTION - Uses GraphQL operation exampleGetEndpoints with sensors "Model" and "BIOS Version". - Paginates using pageInfo.hasNextPage / endCursor until all endpoints are collected. - Optional filtering: provide -ListFile with one computer name per line (NetBIOS or FQDN). - Normalizes names by taking the part before the first dot and uppercasing (so NetBIOS and FQDN match). - Reads URL/token from a local config.json if not passed as parameters. - Optional TLS bypass for lab (-SkipCertCheck or via config.json flag). .USAGE # With config.json (same folder): { "TaniumUrl": "tanium.pp.dktinfra.io", "TaniumApiToken": "token-xxxx", "SkipCertificateCheck": true } ./APICheckBiosVersion.ps1 -Count 200 -Time 30 -MaxAge 900 -PageSize 200 # Filter from a list (one name per line, no header) ./APICheckBiosVersion.ps1 -ListFile .\machines.txt # Grid view and CSV export ./APICheckBiosVersion.ps1 -ListFile .\machines.txt -Grid -ExportCsv C:\Temp\bios.csv # Raw JSON of aggregated edges ./APICheckBiosVersion.ps1 -Raw .PARAMETERS -Url, -Token Override Tanium URL and API token (otherwise read from config.json). -Count, -Time, -MaxAge GraphQL collection knobs (expectedCount, stableWaitTime, maxAge seconds). -PageSize Page size for pagination (GraphQL 'first'). -ListFile Optional path to a simple list (one hostname per line) to filter results. -Raw Output aggregated JSON instead of table/grid. -Grid Show results in Out-GridView (if available). -Pick With -Grid, allow selection and output only the selection. -ExportCsv Path to write CSV export. -SkipCertCheck Ignore TLS cert validation (lab/dev only). .NOTES Requires: PowerShell 5.1+ (Out-GridView on PS7 via Microsoft.PowerShell.GraphicalTools). #> param( [string]$Url, [string]$Token, [switch]$SkipCertCheck, [int]$Count = 200, [int]$Time = 30, [int]$MaxAge = 900, [int]$PageSize = 200, [string]$ListFile, [switch]$Raw, [switch]$Grid, [switch]$Pick, [string]$ExportCsv ) $ErrorActionPreference = 'Stop' try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} # ---------- Helpers ---------- function Get-GatewayUri { param([Parameter(Mandatory)][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/plugin/products/gateway/graphql" } # TLS bypass for Windows PowerShell 5.1 (temporary, restored after call) $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 } } function Get-HostKey { param([string]$Name) if ([string]::IsNullOrWhiteSpace($Name)) { return '' } $n = $Name.Trim() $nb = ($n -split '\.', 2)[0] # take part before first dot if FQDN return $nb.ToUpperInvariant() } function Read-TargetsFromFile { param([Parameter(Mandatory)][string]$Path) if (-not (Test-Path -LiteralPath $Path)) { throw "List file not found: $Path" } $set = New-Object System.Collections.Generic.HashSet[string] Get-Content -LiteralPath $Path -Encoding UTF8 | ForEach-Object { $line = $_.Trim() if (-not [string]::IsNullOrWhiteSpace($line)) { [void]$set.Add((Get-HostKey $line)) } } if ($set.Count -eq 0) { throw "List file is empty: $Path" } return $set } function Get-ValuesForSensor { param([array]$Columns, [string]$SensorName) $match = $Columns | Where-Object { $_.sensor.name -eq $SensorName } if (-not $match) { return '' } $vals = @() foreach ($c in $match) { foreach ($v in $c.values) { if ($null -ne $v -and ("$v").Trim()) { $vals += ("$v") } } } # de-dup while preserving order $uniq = @() foreach ($v in $vals) { if (-not $uniq.Contains($v)) { $uniq += $v } } return ($uniq -join ' | ') } # ---------- 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 } } # ---------- Prepare ---------- $gateway = Get-GatewayUri -HostLike $Url $headers = @{ 'Content-Type' = 'application/json'; session = $Token } $targetSet = $null if ($ListFile) { $targetSet = Read-TargetsFromFile -Path $ListFile } # ---------- GraphQL (paginated) ---------- $Query = @' query exampleGetEndpoints($count: Int, $time: Int, $maxAge: Int, $first: Int, $after: Cursor) { endpoints(source: { ts: { expectedCount: $count, stableWaitTime: $time, maxAge: $maxAge } }, first: $first, after: $after) { edges { node { computerID name serialNumber ipAddress sensorReadings(sensors: [{name: "Model"}, {name: "BIOS Version"}]) { columns { name values sensor { name } } } }} pageInfo { hasNextPage endCursor } } } '@ $allEdges = New-Object System.Collections.Generic.List[object] $cursor = $null while ($true) { $variables = @{ count = $Count; time = $Time; maxAge = $MaxAge; first = $PageSize; after = $cursor } $bodyObj = @{ query = $Query; variables = $variables; operationName = 'exampleGetEndpoints' } $bodyJson = $bodyObj | ConvertTo-Json -Depth 12 $resp = $null $ps7 = ($PSVersionTable.PSVersion.Major -ge 7) if ($ps7 -and $SkipCertCheck) { $resp = Invoke-RestMethod -SkipCertificateCheck -Method Post -Uri $gateway -Headers $headers -ContentType 'application/json' -Body $bodyJson } else { try { Enter-InsecureTls -Enable:$SkipCertCheck $resp = Invoke-RestMethod -Method Post -Uri $gateway -Headers $headers -ContentType 'application/json' -Body $bodyJson } finally { Exit-InsecureTls } } if ($resp.errors) { throw ("GraphQL errors: " + (($resp.errors | ForEach-Object { $_.message }) -join '; ')) } $edges = $resp.data.endpoints.edges if ($edges) { $allEdges.AddRange($edges) } $pi = $resp.data.endpoints.pageInfo if (-not $pi -or -not $pi.hasNextPage) { break } $cursor = $pi.endCursor } if ($Raw) { [pscustomobject]@{ total = $allEdges.Count; edges = $allEdges } | ConvertTo-Json -Depth 14 return } if ($allEdges.Count -eq 0) { Write-Output 'Aucun endpoint retourné.'; return } # ---------- Filter (optional) + Output ---------- $rows = foreach ($edge in $allEdges) { $n = $edge.node $nb = Get-HostKey $n.name if ($targetSet -and -not $targetSet.Contains($nb)) { continue } $cols = $n.sensorReadings.columns [pscustomobject]@{ Nom = $nb Modèle = (Get-ValuesForSensor -Columns $cols -SensorName 'Model') VersionBIOS = (Get-ValuesForSensor -Columns $cols -SensorName 'BIOS Version') } } if (-not $rows) { Write-Output 'Aucun poste correspondant.'; return } $rowsSorted = $rows | Sort-Object Nom # Optional CSV export 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)" } } # Grid mode if ($Grid) { $title = "BIOS Versions — {0} poste(s)" -f ($rowsSorted.Count) if (Get-Command Out-GridView -ErrorAction SilentlyContinue) { if ($Pick) { $sel = $rowsSorted | Out-GridView -Title $title -PassThru if ($sel) { $sel | Format-Table -AutoSize } return } else { $rowsSorted | Out-GridView -Title $title return } } else { Write-Warning "Out-GridView n'est pas disponible. Sous PowerShell 7+, installe: Install-Module Microsoft.PowerShell.GraphicalTools" } } # Default console table $rowsSorted | Format-Table -AutoSize