Auto-commit: 2025-10-31 08:59:02

This commit is contained in:
David Wuibaille
2025-10-31 08:59:02 +01:00
parent d3b18d8b45
commit 851c85ec3d
30 changed files with 3734 additions and 6 deletions

106
API/ComputerGroup - Get.ps1 Normal file
View File

@@ -0,0 +1,106 @@
<#
.SYNOPSIS
Initialize Tanium session from config.json (no env vars). Prefer -CredentialObject (hashtable).
Falls back to -BaseURI/-Token or -BaseURI/-ApiToken, then ephemeral CLIXML if needed.
#>
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
Import-Module Redden-TanREST -Force
# --- Load config.json ---
$configPath = Join-Path $PSScriptRoot 'config.json'
if (-not (Test-Path $configPath)) { throw "Configuration file not found: $configPath" }
Write-Host "Reading configuration from: $configPath"
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
$TaniumUrl = $config.TaniumUrl
$TaniumApiToken = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumApiToken)) {
throw "Both TaniumUrl and TaniumApiToken must be provided in config.json."
}
# Normalize to bare host (strip scheme and trailing slash)
$BaseUriHost = (($TaniumUrl -replace '^https?://','') -replace '/+$','')
$SecureToken = $TaniumApiToken | ConvertTo-SecureString -AsPlainText -Force
# --- Initialize session (feature-detected) ---
Write-Host "Initializing Tanium session..."
$cmd = Get-Command Initialize-TaniumSession -ErrorAction Stop
$paramNames = $cmd.Parameters.Keys
$initialized = $false
$lastError = $null
# 1) Preferred: -CredentialObject (expects a hashtable with ContainsKey)
if (-not $initialized -and ($paramNames -contains 'CredentialObject')) {
try {
$credHash = @{
baseURI = $BaseUriHost
token = $SecureToken
}
Initialize-TaniumSession -CredentialObject $credHash
$initialized = $true
Write-Host "Session initialized via -CredentialObject (hashtable)."
} catch { $lastError = $_ }
}
# 2) Fallback: -BaseURI/-Token (token may be SecureString or string depending on module)
if (-not $initialized -and ($paramNames -contains 'BaseURI') -and ($paramNames -contains 'Token')) {
try {
Initialize-TaniumSession -BaseURI $BaseUriHost -Token $SecureToken
$initialized = $true
Write-Host "Session initialized via -BaseURI/-Token (SecureString)."
} catch {
$lastError = $_
try {
Initialize-TaniumSession -BaseURI $BaseUriHost -Token $TaniumApiToken
$initialized = $true
Write-Host "Session initialized via -BaseURI/-Token (plain string)."
} catch { $lastError = $_ }
}
}
# 3) Fallback: -BaseURI/-ApiToken (some versions use ApiToken)
if (-not $initialized -and ($paramNames -contains 'BaseURI') -and ($paramNames -contains 'ApiToken')) {
try {
Initialize-TaniumSession -BaseURI $BaseUriHost -ApiToken $SecureToken
$initialized = $true
Write-Host "Session initialized via -BaseURI/-ApiToken (SecureString)."
} catch {
$lastError = $_
try {
Initialize-TaniumSession -BaseURI $BaseUriHost -ApiToken $TaniumApiToken
$initialized = $true
Write-Host "Session initialized via -BaseURI/-ApiToken (plain string)."
} catch { $lastError = $_ }
}
}
# 4) Last resort: ephemeral CLIXML (-PathToXML), then cleanup
if (-not $initialized -and ($paramNames -contains 'PathToXML')) {
try {
$TempXml = Join-Path $env:TEMP ('tanium-session-{0}.apicred' -f ([guid]::NewGuid()))
@{ baseURI = $BaseUriHost; token = $SecureToken } | Export-Clixml -Path $TempXml
Initialize-TaniumSession -PathToXML $TempXml
Remove-Item $TempXml -Force -ErrorAction SilentlyContinue
$initialized = $true
Write-Host "Session initialized via -PathToXML (ephemeral file removed)."
} catch { $lastError = $_ }
}
if (-not $initialized) {
Write-Error "Failed to initialize Tanium session. Last error: $($lastError.Exception.Message)"
throw
}
# --- Retrieve & display groups ---
Write-Host "Retrieving all Computer Groups..."
$groups = Get-ComputerGroup -All
if (Get-Command Out-GridView -ErrorAction SilentlyContinue) {
$groups | Out-GridView -Title 'Tanium Computer Groups'
} else {
Write-Warning "Out-GridView not available; showing a console table instead."
$groups | Format-Table -Auto
}

115
API/ComputerGroup - New.ps1 Normal file
View File

@@ -0,0 +1,115 @@
<#
.SYNOPSIS
Initialize Tanium session from config.json, then create Computer Groups only if they do not already exist.
#>
# =========================
# Block 1 - Prerequisites
# =========================
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
Import-Module Redden-TanREST -Force
# =========================
# Block 2 - Load config.json & init session
# =========================
$configPath = Join-Path $PSScriptRoot 'config.json'
$TempXml = Join-Path $env:TEMP 'tanium-session-tmp.apicred'
try {
if (-not (Test-Path $configPath)) {
throw "Configuration file not found: $configPath"
}
Write-Host "Reading configuration from: $configPath"
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
# Values (fallback to environment variables)
$TaniumUrl = $config.TaniumUrl
$TaniumApiToken = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl)) { $TaniumUrl = $env:TANIUM_URL }
if ([string]::IsNullOrWhiteSpace($TaniumApiToken)) { $TaniumApiToken = $env:TANIUM_TOKEN }
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumApiToken)) {
throw "Both TaniumUrl and TaniumApiToken must be provided (config.json or environment variables)."
}
# Normalize: bare host (no scheme / trailing slash)
if ($TaniumUrl -match '^https?://') {
$TaniumUrl = $TaniumUrl -replace '^https?://', '' -replace '/+$', ''
Write-Host "Normalized TaniumUrl to host: $TaniumUrl"
}
# Build temporary CLIXML for Initialize-TaniumSession
$ExportObject = @{
baseURI = $TaniumUrl
token = ($TaniumApiToken | ConvertTo-SecureString -AsPlainText -Force)
}
Write-Host "Writing temporary CLIXML to: $TempXml"
$ExportObject | Export-Clixml -Path $TempXml
Write-Host "Initializing Tanium session..."
Initialize-TaniumSession -PathToXML $TempXml
Write-Host "Tanium session initialized successfully."
}
catch {
Write-Error "Failed to initialize Tanium session. Details: $($_.Exception.Message)"
throw
}
try {
# =========================
# Block 3 - Ensure Computer Groups exist
# =========================
# Hashtable of groups to ensure: Name => Filter text
$ComputerGroups = @{
"David_LTSC2019" = "(Computer Name contains 2019)"
"David_LTSC2021" = "(Computer Name contains 2021)"
"David_LTSC2024" = "(Computer Name contains 2024)"
}
# Default Content Set (id = 0) change if needed
$contentSetDefault = [pscustomobject]@{ id = 0 }
Write-Host "Ensuring Computer Groups exist (create if missing)..."
foreach ($kv in $ComputerGroups.GetEnumerator()) {
$name = $kv.Key
$filterText = $kv.Value
try {
$existing = Get-ComputerGroup -Name $name -ErrorAction SilentlyContinue
if ($existing) {
Write-Host "Group '$name' already exists (Id: $($existing.id)). Skipping."
continue
}
Write-Host "Creating group: '$name' with filter: $filterText"
New-ComputerGroup `
-Name $name `
-Type 0 `
-Text $filterText `
-Content_Set $contentSetDefault `
-Filter_Flag $true `
-Management_Rights_Flag $true
Write-Host "Created group: '$name'."
}
catch {
Write-Error "Failed processing group '$name'. Details: $($_.Exception.Message)"
}
}
}
finally {
# =========================
# Block 4 - Cleanup
# =========================
try {
if (Test-Path $TempXml) {
Remove-Item $TempXml -Force -ErrorAction SilentlyContinue
Write-Host "Temporary CLIXML removed: $TempXml"
}
} catch {
Write-Warning "Could not remove temporary CLIXML ($TempXml): $($_.Exception.Message)"
}
}

196
API/Deploy - Create.ps1 Normal file
View File

@@ -0,0 +1,196 @@
<#
.SYNOPSIS
Create a Tanium Deploy software package using the Deploy module cmdlets.
Strategy: clone an existing package to get the exact schema, then modify a few fields.
REQUIRES: Redden-TanREST (for the Deploy cmdlets your screenshot shows)
#>
# =============== Block 0 - Parameters to edit ===============
# New package name and install command
$NewPackageName = 'Demo - My Package (API)'
$InstallCommandLine = 'cmd /c echo Hello from Deploy > %TEMP%\hello.txt'
# Choose ONE mode: 'CommandOnly' | 'LocalFile' | 'RemoteURL'
$Mode = 'CommandOnly'
# If Mode = LocalFile
$LocalFilePath = 'C:\Temp\payload.msi'
# If Mode = RemoteURL
$RemoteFileUrl = 'https://example.com/payload.msi'
# An existing simple package to use as a schema template (must exist in your Deploy)
$TemplatePackageName = 'Notepad++ (Gallery)' # <-- mets ici un paquet simple qui existe chez toi
# Optional: content set ID (leave as-is to keep the template's)
$OverrideContentSetId = $null # ex: 0 (ou $null pour garder lID du template)
# =============== Block 1 - Import & Session ===============
$ErrorActionPreference = 'Stop'
Import-Module Redden-TanREST -Force
# Load config.json next to this script
$configPath = Join-Path $PSScriptRoot 'config.json'
if (-not (Test-Path $configPath)) { throw "Configuration file not found: $configPath" }
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
$TaniumUrl = $config.TaniumUrl
$TaniumToken = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumToken)) {
throw "Both TaniumUrl and TaniumApiToken must be provided in config.json."
}
if ($TaniumUrl -match '^https?://') { $TaniumUrl = $TaniumUrl -replace '^https?://','' -replace '/+$','' }
# Create a short-lived CLIXML for Initialize-TaniumSession (same pattern as your other scripts)
$TempXml = Join-Path $env:TEMP 'tanium-session-tmp.apicred'
$ExportObject = @{
baseURI = $TaniumUrl
token = ($TaniumToken | ConvertTo-SecureString -AsPlainText -Force)
}
$ExportObject | Export-Clixml -Path $TempXml
Initialize-TaniumSession -PathToXML $TempXml
Write-Host "Tanium session initialized."
# =============== Block 2 - Helpers ===============
function DeepClone($obj) {
# round-trip through JSON for a deep copy
return ($obj | ConvertTo-Json -Depth 100 | ConvertFrom-Json)
}
function Remove-ReadOnlyProps {
param([Parameter(Mandatory)]$o)
# remove common read-only or server-managed fields wherever they appear
$remove = @('id','createdAt','createdBy','updatedAt','updatedBy','lastModified','created','modified','revision','status')
if ($o -is [System.Collections.IDictionary]) {
foreach ($k in @($o.Keys)) {
if ($remove -contains [string]$k) { $o.Remove($k) | Out-Null; continue }
$o[$k] = Remove-ReadOnlyProps $o[$k]
}
return $o
} elseif ($o -is [System.Collections.IEnumerable] -and -not ($o -is [string])) {
$new = @()
foreach ($item in $o) { $new += ,(Remove-ReadOnlyProps $item) }
return $new
} else {
return $o
}
}
function Replace-AllTempFileIds {
param(
[Parameter(Mandatory)]$o,
[Parameter(Mandatory)][string]$NewTempFileId
)
# replace any property named 'tempFileId' or 'fileId' with the new tempfile id (common API shapes)
$matchKeys = @('tempFileId','fileId')
if ($o -is [System.Collections.IDictionary]) {
foreach ($k in @($o.Keys)) {
if ($matchKeys -contains [string]$k) {
$o[$k] = $NewTempFileId
} else {
$o[$k] = Replace-AllTempFileIds -o $o[$k] -NewTempFileId $NewTempFileId
}
}
return $o
} elseif ($o -is [System.Collections.IEnumerable] -and -not ($o -is [string])) {
$new = @()
foreach ($item in $o) { $new += ,(Replace-AllTempFileIds -o $item -NewTempFileId $NewTempFileId) }
return $new
} else {
return $o
}
}
function Update-InstallCommand {
param(
[Parameter(Mandatory)]$o,
[Parameter(Mandatory)][string]$CommandLine
)
# best-effort: update common command fields if found
$cmdKeys = @('command','commandLine','installCommand','install_command','command_line')
if ($o -is [System.Collections.IDictionary]) {
foreach ($k in @($o.Keys)) {
if ($cmdKeys -contains [string]$k -and ($o[$k] -is [string])) {
$o[$k] = $CommandLine
} else {
$o[$k] = Update-InstallCommand -o $o[$k] -CommandLine $CommandLine
}
}
return $o
} elseif ($o -is [System.Collections.IEnumerable] -and -not ($o -is [string])) {
$new = @()
foreach ($item in $o) { $new += ,(Update-InstallCommand -o $item -CommandLine $CommandLine) }
return $new
} else {
return $o
}
}
# =============== Block 3 - Fetch template & prepare body ===============
Write-Host "Fetching template package: $TemplatePackageName"
$template = Get-DeploySoftwarePackage -Name $TemplatePackageName -IncludeHidden
if (-not $template) { throw "Template package '$TemplatePackageName' not found." }
# If multiple, take the first
if ($template -is [System.Array]) { $template = $template[0] }
# Deep clone and strip read-only fields
$bodyObj = DeepClone $template
$bodyObj = Remove-ReadOnlyProps $bodyObj
# Apply basic changes
$bodyObj.name = $NewPackageName
if ($OverrideContentSetId -ne $null) {
if ($bodyObj.contentSet -and $bodyObj.contentSet.id -ne $null) {
$bodyObj.contentSet.id = [int]$OverrideContentSetId
}
}
# Update command line everywhere it appears (best-effort)
$bodyObj = Update-InstallCommand -o $bodyObj -CommandLine $InstallCommandLine
# =============== Block 4 - Attach file if requested ===============
$tempFile = $null
switch ($Mode) {
'LocalFile' {
Write-Host "Uploading local file: $LocalFilePath"
if (-not (Test-Path $LocalFilePath)) { throw "Local file not found: $LocalFilePath" }
$tempFile = New-DeployTempFile -FilePath $LocalFilePath
if (-not $tempFile) { throw "Failed to upload local file." }
$bodyObj = Replace-AllTempFileIds -o $bodyObj -NewTempFileId ([string]$tempFile.id)
}
'RemoteURL' {
Write-Host "Registering remote file URL: $RemoteFileUrl"
$tempFile = New-DeployTempFile -RemoteURL $RemoteFileUrl
if (-not $tempFile) { throw "Failed to register remote URL." }
$bodyObj = Replace-AllTempFileIds -o $bodyObj -NewTempFileId ([string]$tempFile.id)
}
'CommandOnly' {
Write-Host "No file attachment (command-only mode)."
}
default { throw "Unknown Mode '$Mode' (use CommandOnly | LocalFile | RemoteURL)" }
}
# =============== Block 5 - Create software package ===============
# Convert to JSON for -Body
$bodyJson = $bodyObj | ConvertTo-Json -Depth 100
Write-Host "Creating Deploy software package: $NewPackageName"
$newPkg = New-DeploySoftwarePackage -Body ($bodyJson | ConvertFrom-Json)
# Certains modules attendent un objet PS, dautres une chaîne JSON ; si erreur, essaie directement -Body $bodyJson
if (-not $newPkg) {
Write-Warning "New-DeploySoftwarePackage returned no object. Verify in console if the package exists."
} else {
Write-Host "Created: ID=$($newPkg.id) Name=$($newPkg.name)"
}
# Optional: verify by name
$check = Get-DeploySoftwarePackage -Name $NewPackageName -IncludeHidden
if ($check) {
Write-Host "Verified package exists. Done."
} else {
Write-Warning "Package not found via Get-DeploySoftwarePackage; please check the console or API logs."
}
# Cleanup temp clixml (optional)
Remove-Item -Path $TempXml -Force -ErrorAction SilentlyContinue

59
API/Deploy - Get.ps1 Normal file
View File

@@ -0,0 +1,59 @@
<#
.SYNOPSIS
List Tanium Deploy Software Packages using Get-DeploySoftware.
Reads URL/token from config.json, initializes the session, then queries by:
- All | ByName | ByID | ByVendor | ByCommand | NameRegex
and displays results in Out-GridView (fallback to console table).
#>
# =========================
# Block 1 - Prerequisites
# =========================
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
Import-Module Redden-TanREST -Force
# =========================
# Block 2 - Load config & init session
# =========================
$configPath = Join-Path $PSScriptRoot 'config.json'
$TempXml = Join-Path $env:TEMP 'tanium-session-tmp.apicred'
if (-not (Test-Path $configPath)) { throw "Configuration file not found: $configPath" }
Write-Host "Reading configuration from: $configPath"
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
$TaniumUrl = $config.TaniumUrl
$TaniumToken = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumToken)) {
throw "Both TaniumUrl and TaniumApiToken must be provided (config.json or environment variables)."
}
if ($TaniumUrl -match '^https?://') {
$TaniumUrl = $TaniumUrl -replace '^https?://','' -replace '/+$',''
Write-Host "Normalized TaniumUrl to host: $TaniumUrl"
}
$ExportObject = @{
baseURI = $TaniumUrl
token = ($TaniumToken | ConvertTo-SecureString -AsPlainText -Force)
}
$ExportObject | Export-Clixml -Path $TempXml
Write-Host "Initializing Tanium session..."
Initialize-TaniumSession -PathToXML $TempXml
Write-Host "Tanium session initialized."
# =========================
# Block 3 - Choose query mode
# =========================
Get-DeploySoftware -All | Out-GridView
# =========================
# Block 5 - Cleanup
# =========================
if (Test-Path $TempXml) {
Remove-Item $TempXml -Force -ErrorAction SilentlyContinue
Write-Host "Temporary CLIXML removed: $TempXml"
}

View File

@@ -0,0 +1,93 @@
<#
.SYNOPSIS
Initialize Tanium session from config.json, ensure computer groups exist (create if missing),
then ask an Interact question and display results in Out-GridView.
.NOTES
- Keep config.json out of version control.
#>
# =========================
# Block 1 - Prerequisites
# =========================
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
Import-Module Redden-TanREST -Force
# =========================
# Block 2 - Load config.json & init session
# =========================
$configPath = Join-Path $PSScriptRoot 'config.json'
try {
if (-not (Test-Path $configPath)) {
throw "Configuration file not found: $configPath"
}
Write-Host "Reading configuration from: $configPath"
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
# Values (fallback to environment variables)
$TaniumUrl = $config.TaniumUrl
$TaniumApiToken = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl)) { $TaniumUrl = $env:TANIUM_URL }
if ([string]::IsNullOrWhiteSpace($TaniumApiToken)) { $TaniumApiToken = $env:TANIUM_TOKEN }
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumApiToken)) {
throw "Both TaniumUrl and TaniumApiToken must be provided (config.json or environment variables)."
}
# Normalize: bare host (no scheme / trailing slash)
if ($TaniumUrl -match '^https?://') {
$TaniumUrl = $TaniumUrl -replace '^https?://', '' -replace '/+$', ''
Write-Host "Normalized TaniumUrl to host: $TaniumUrl"
}
# Build temporary CLIXML for Initialize-TaniumSession
$TempXml = Join-Path $env:TEMP 'tanium-session-tmp.apicred'
$ExportObject = @{
baseURI = $TaniumUrl
token = ($TaniumApiToken | ConvertTo-SecureString -AsPlainText -Force)
}
Write-Host "Writing temporary CLIXML to: $TempXml"
$ExportObject | Export-Clixml -Path $TempXml
Write-Host "Initializing Tanium session..."
Initialize-TaniumSession -PathToXML $TempXml
Write-Host "Tanium session initialized successfully."
}
catch {
Write-Error "Failed to initialize Tanium session. Details: $($_.Exception.Message)"
throw
}
# =========================
# Block 3 - Interact question + Out-GridView (no flattener)
# =========================
$canonicalText = 'Get Computer Name and IP Address from all machines with Operating System contains Windows'
$minResponsePct = 95
$expireSeconds = 1200
Write-Host "Asking Interact question..."
$result = Get-InteractQuestionResult `
-CanonicalText $canonicalText `
-MinResponsePercent $minResponsePct `
-ExpireSeconds $expireSeconds `
-Verbose
$result | Out-GridView -Title 'Windows endpoints'
# =========================
# Block 5 - Cleanup
# =========================
try {
if (Test-Path $TempXml) {
Remove-Item $TempXml -Force -ErrorAction SilentlyContinue
Write-Host "Temporary CLIXML removed: $TempXml"
}
} catch {
Write-Warning "Could not remove temporary CLIXML ($TempXml): $($_.Exception.Message)"
}

208
API/Package - Create.ps1 Normal file
View File

@@ -0,0 +1,208 @@
<#
.SYNOPSIS
Create Tanium action packages from a definition array supporting three modes:
- CommandOnly : only a command (no files)
- Upload : upload local files into the package, then execute from .\__Download\
- Url : reference a remote file by URL with SHA-256 and "check for update" TTL
The script:
1) Initializes a Tanium session from config.json (same folder as the script),
2) Iterates over $packages and creates each package accordingly,
3) Optionally uploads payloads (Upload mode), or references URL files (Url mode),
4) Cleans up the temporary CLIXML.
NOTES
- Keep config.json out of version control.
- When executing an uploaded file, always reference it via .\__Download\<file> in -Command.
#>
# =========================
# Block 1 - Prerequisites
# =========================
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
Import-Module Redden-TanREST -Force
# =========================
# Block 2 - Load config.json & init session
# =========================
$configPath = Join-Path $PSScriptRoot 'config.json'
$TempXml = Join-Path $env:TEMP 'tanium-session-tmp.apicred'
try {
if (-not (Test-Path $configPath)) { throw "Configuration file not found: $configPath" }
Write-Host "Reading configuration from: $configPath"
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
$TaniumUrl = $config.TaniumUrl
$TaniumApiToken = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl)) { $TaniumUrl = $env:TANIUM_URL }
if ([string]::IsNullOrWhiteSpace($TaniumApiToken)) { $TaniumApiToken = $env:TANIUM_TOKEN }
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumApiToken)) {
throw "Both TaniumUrl and TaniumApiToken must be provided (config.json or environment variables)."
}
# Normalize: bare host (no scheme / trailing slash)
if ($TaniumUrl -match '^https?://') {
$TaniumUrl = $TaniumUrl -replace '^https?://', '' -replace '/+$', ''
Write-Host "Normalized TaniumUrl to host: $TaniumUrl"
}
# Build temporary CLIXML for Initialize-TaniumSession
$ExportObject = @{
baseURI = $TaniumUrl
token = ($TaniumApiToken | ConvertTo-SecureString -AsPlainText -Force)
}
Write-Host "Writing temporary CLIXML to: $TempXml"
$ExportObject | Export-Clixml -Path $TempXml
Write-Host "Initializing Tanium session..."
Initialize-TaniumSession -PathToXML $TempXml
Write-Host "Tanium session initialized successfully."
}
catch {
Write-Error "Failed to initialize Tanium session. Details: $($_.Exception.Message)"
throw
}
# =========================
# Block 3 - Global settings
# =========================
$commandTimeout = 600 # seconds for New-TaniumPackage -Command_Timeout (default is 60)
$packageTTL = 3600 # seconds for New-TaniumPackage -Expire_Seconds (default is 660)
# Helper: optional SHA-256 calculator for a remote URL (only used if you don't supply it)
function Get-RemoteFileSha256 {
param([Parameter(Mandatory)][string]$Url)
$tmp = Join-Path $env:TEMP ([IO.Path]::GetRandomFileName())
try {
Invoke-WebRequest -Uri $Url -OutFile $tmp
(Get-FileHash -Algorithm SHA256 -Path $tmp).Hash.ToLowerInvariant()
} finally {
Remove-Item $tmp -Force -ErrorAction SilentlyContinue
}
}
# =========================
# Block 4 - Packages to create (3 cases)
# =========================
$packages = @(
# 1) Command only (no files)
@{
Name = 'Case1 - Command only'
Command = 'cmd /c ipconfig /all > %TEMP%\net.txt'
Mode = 'CommandOnly'
ContentSetID = 0
},
# 2) Upload local files (put files in UploadFolder; reference via .\__Download\ in Command)
@{
Name = 'Case2 - Upload a file'
Command = 'cmd /c cscript.exe .\__Download\cleanup.vbs'
Mode = 'Upload'
UploadFolder = 'C:\temp\pkg-cleanup' # must contain cleanup.vbs (and any other needed files)
ContentSetID = 2
},
# 3) URL file with "check for update" TTL (+ required SHA-256)
@{
Name = 'Case3 - URL file with update check'
Command = 'cmd /c cscript.exe .\__Download\remove-sample-files.vbs'
Mode = 'Url'
UrlFile = @{
Url = 'https://example.com/remove-sample-files.vbs'
Name = 'remove-sample-files.vbs'
Sha256 = '' # if empty, the script will compute it
CheckForUpdateSeconds = 86400 # e.g. 1 day; 0 = Never
}
ContentSetID = 2
}
)
# =========================
# Block 5 - Creation loop
# =========================
foreach ($pkg in $packages) {
Write-Host "`n=== Package: $($pkg.Name) ==="
try {
switch ($pkg.Mode) {
'CommandOnly' {
$newPkg = New-TaniumPackage `
-Name $pkg.Name `
-Command $pkg.Command `
-Expire_Seconds $packageTTL `
-Command_Timeout $commandTimeout `
-ContentSetID $pkg.ContentSetID
Write-Host "Created (ID=$($newPkg.id))"
}
'Upload' {
if (-not (Test-Path $pkg.UploadFolder)) {
throw "Upload folder not found: $($pkg.UploadFolder)"
}
$newPkg = New-TaniumPackage `
-Name $pkg.Name `
-Command $pkg.Command `
-Expire_Seconds $packageTTL `
-Command_Timeout $commandTimeout `
-ContentSetID $pkg.ContentSetID
Write-Host "Created (ID=$($newPkg.id)); uploading payload..."
Update-ActionPackageFile -PackageID $newPkg.id -UploadFolder $pkg.UploadFolder
Write-Host "Payload uploaded."
}
'Url' {
# Build Files[] entry expected by the API
$sha = $pkg.UrlFile.Sha256
if ([string]::IsNullOrWhiteSpace($sha)) {
Write-Host "Computing SHA-256 for URL: $($pkg.UrlFile.Url)"
$sha = Get-RemoteFileSha256 -Url $pkg.UrlFile.Url
}
$fileObj = [pscustomobject]@{
name = $pkg.UrlFile.Name
url = $pkg.UrlFile.Url
hash = $sha
# The property below controls "Check for update" TTL in seconds.
# If your Tanium uses a different name (e.g. cache_ttl_seconds/refresh_seconds), adjust here:
check_for_update_seconds = $pkg.UrlFile.CheckForUpdateSeconds
}
$newPkg = New-TaniumPackage `
-Name $pkg.Name `
-Command $pkg.Command `
-Expire_Seconds $packageTTL `
-Command_Timeout $commandTimeout `
-ContentSetID $pkg.ContentSetID `
-Files @($fileObj)
Write-Host "Created (ID=$($newPkg.id)) with remote file URL."
}
default {
throw "Unknown Mode for package '$($pkg.Name)'. Use 'CommandOnly' | 'Upload' | 'Url'."
}
}
}
catch {
Write-Error "Failed to process package '$($pkg.Name)': $($_.Exception.Message)"
}
}
# =========================
# Block 6 - Cleanup
# =========================
try {
if (Test-Path $TempXml) {
Remove-Item $TempXml -Force -ErrorAction SilentlyContinue
Write-Host "Temporary CLIXML removed: $TempXml"
}
} catch {
Write-Warning "Could not remove temporary CLIXML ($TempXml): $($_.Exception.Message)"
}

139
API/Package - Get.ps1 Normal file
View File

@@ -0,0 +1,139 @@
<#
.SYNOPSIS
Initialize Tanium session from config.json, then list Tanium Packages
(using Get-TaniumPackage in several modes) and show them in Out-GridView.
#>
# =========================
# Block 1 - Prerequisites
# =========================
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
Import-Module Redden-TanREST -Force
# =========================
# Block 2 - Load config.json & init session
# =========================
$configPath = Join-Path $PSScriptRoot 'config.json'
$TempXml = Join-Path $env:TEMP 'tanium-session-tmp.apicred'
try {
if (-not (Test-Path $configPath)) { throw "Configuration file not found: $configPath" }
Write-Host "Reading configuration from: $configPath"
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
$TaniumUrl = $config.TaniumUrl
$TaniumApiToken = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl)) { $TaniumUrl = $env:TANIUM_URL }
if ([string]::IsNullOrWhiteSpace($TaniumApiToken)) { $TaniumApiToken = $env:TANIUM_TOKEN }
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumApiToken)) {
throw "Both TaniumUrl and TaniumApiToken must be provided (config.json or environment variables)."
}
# Normalize: bare host (no scheme / trailing slash)
if ($TaniumUrl -match '^https?://') {
$TaniumUrl = $TaniumUrl -replace '^https?://', '' -replace '/+$', ''
Write-Host "Normalized TaniumUrl to host: $TaniumUrl"
}
# Temporary CLIXML for Initialize-TaniumSession
$ExportObject = @{
baseURI = $TaniumUrl
token = ($TaniumApiToken | ConvertTo-SecureString -AsPlainText -Force)
}
Write-Host "Writing temporary CLIXML to: $TempXml"
$ExportObject | Export-Clixml -Path $TempXml
Write-Host "Initializing Tanium session..."
Initialize-TaniumSession -PathToXML $TempXml
Write-Host "Tanium session initialized successfully."
}
catch {
Write-Error "Failed to initialize Tanium session. Details: $($_.Exception.Message)"
throw
}
# =========================
# Block 3 - Pick your query mode
# =========================
# Choose ONE mode: 'All' | 'ByName' | 'ByID' | 'NameRegex' | 'ByParam'
$QueryMode = 'All'
$IncludeHidden = $false
# Fill only the variables needed by the chosen mode:
$ByName_Name = 'My Awesome Package'
$ByID_Id = 123
$NameRegex_Pattern= '.*Awesome.*'
$ByParam_Field = 'command' # e.g. 'command'
$ByParam_Operator = 'RegexMatch' # e.g. 'Equal' | 'RegexMatch'
$ByParam_Value = '.*cleanup\.vbs' # regex string if using RegexMatch
$ByParam_Type = 'String' # 'String' | 'Version' | 'Numeric' | 'IPAddress' | 'Date' | 'DataSize' | 'NumericInteger'
# =========================
# Block 4 - Retrieve packages
# =========================
try {
switch ($QueryMode) {
'All' {
Write-Host "Retrieving ALL packages..."
$packages = Get-TaniumPackage -All -IncludeHidden:$IncludeHidden
}
'ByName' {
Write-Host "Retrieving package by name: $ByName_Name"
$packages = Get-TaniumPackage -Name $ByName_Name -IncludeHidden:$IncludeHidden
}
'ByID' {
Write-Host "Retrieving package by ID: $ByID_Id"
$packages = Get-TaniumPackage -ID $ByID_Id -IncludeHidden:$IncludeHidden
}
'NameRegex' {
Write-Host "Retrieving packages by NameRegex: $NameRegex_Pattern"
$packages = Get-TaniumPackage -NameRegex $NameRegex_Pattern -IncludeHidden:$IncludeHidden
}
'ByParam' {
Write-Host "Retrieving packages by field filter: $ByParam_Field $ByParam_Operator $ByParam_Value ($ByParam_Type)"
$packages = Get-TaniumPackage -Field $ByParam_Field -Operator $ByParam_Operator -Value $ByParam_Value -Type $ByParam_Type -IncludeHidden:$IncludeHidden
}
default {
throw "Unknown QueryMode '$QueryMode'. Use: All | ByName | ByID | NameRegex | ByParam."
}
}
if (-not $packages) {
Write-Warning "No packages found."
}
# Try to present common fields; unknown props will appear blank (that's fine)
$view = $packages | Select-Object `
@{n='id';e={$_.id}},
@{n='name';e={$_.name}},
@{n='display_name';e={$_.display_name}},
@{n='command';e={$_.command}},
@{n='expire_seconds';e={$_.expire_seconds}},
@{n='command_timeout';e={$_.command_timeout}},
@{n='content_set_id';e={$_.content_set.id}},
@{n='hidden';e={$_.hidden}}
if (Get-Command Out-GridView -ErrorAction SilentlyContinue) {
$view | Out-GridView -Title "Tanium Packages ($QueryMode)"
} else {
Write-Warning "Out-GridView not available; showing a console table instead."
$view | Format-Table -Auto
}
}
catch {
Write-Error "Failed to retrieve/display packages. Details: $($_.Exception.Message)"
}
# =========================
# Block 5 - Cleanup
# =========================
try {
if (Test-Path $TempXml) {
Remove-Item $TempXml -Force -ErrorAction SilentlyContinue
Write-Host "Temporary CLIXML removed: $TempXml"
}
} catch {
Write-Warning "Could not remove temporary CLIXML ($TempXml): $($_.Exception.Message)"
}

View File

@@ -0,0 +1,218 @@
<#
.SYNOPSIS
Create or delete a Tanium Computer Group via Gateway GraphQL.
.DESCRIPTION
- CREATE: filter "Computer Name STARTS_WITH <Prefix>" (usable as Computer Management Group; optional Content Set).
- DELETE: delete a Computer Group by its Tanium ID.
- Reads URL/token from config.json (same folder) unless -Url/-Token are provided.
- Optional TLS bypass for lab with -SkipCertCheck.
.USAGE (examples)
# Create a group (no defaults; you MUST pass -GroupName and -Prefix)
.\Create-ComputerGroup.ps1 -GroupName "LAB - starts with LAB" -Prefix "LAB" -MrEnabled:$true
.\Create-ComputerGroup.ps1 -GroupName "LAB - starts with LAB" -Prefix "LAB" -ContentSetName "My Content Set"
# Delete by ID
.\Create-ComputerGroup.ps1 -Delete -Id "12345"
# Override URL/token
.\Create-ComputerGroup.ps1 -Url tanium.pp.dktinfra.io -Token 'token-xxxx' -GroupName "LAB ..." -Prefix "LAB"
# Raw JSON (debug)
.\Create-ComputerGroup.ps1 -GroupName "LAB ..." -Prefix "LAB" -Raw
#>
param(
[string]$Url,
[string]$Token,
[switch]$SkipCertCheck,
# CREATE params (no defaults; both required for creation)
[string]$GroupName,
[string]$Prefix,
[bool] $MrEnabled = $true,
[string]$ContentSetName,
# DELETE params
[switch]$Delete,
[string]$Id,
[switch]$Raw
)
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
function Show-ShortHelp {
Write-Host ""
Write-Host "Create mode (requires -GroupName and -Prefix):" -ForegroundColor Cyan
Write-Host " .\Create-ComputerGroup.ps1 -GroupName 'LAB - starts with LAB' -Prefix 'LAB' [-MrEnabled:`$true] [-ContentSetName 'My Content Set']" -ForegroundColor Gray
Write-Host ""
Write-Host "Delete mode (requires -Delete and -Id):" -ForegroundColor Cyan
Write-Host " .\Create-ComputerGroup.ps1 -Delete -Id '12345'" -ForegroundColor Gray
Write-Host ""
Write-Host "Common options: -Url <host> -Token <token> -SkipCertCheck -Raw" -ForegroundColor DarkGray
}
# ---------- 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
}
}
# ---------- 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 }
}
# ---------- Endpoint & headers ----------
$gateway = Get-GatewayUri -HostLike $Url
$headers = @{ 'Content-Type' = 'application/json'; session = $Token }
# ---------- GraphQL mutations ----------
$Create_WithContentSet = @'
mutation createGroup($name: String!, $mrEnabled: Boolean!, $prefix: String!, $contentSetName: String!) {
computerGroupCreate(
input: {
name: $name
managementRightsEnabled: $mrEnabled
contentSetRef: { name: $contentSetName }
filter: {
sensor: { name: "Computer Name" }
op: STARTS_WITH
value: $prefix
}
}
) {
group { id name }
error { message }
}
}
'@
$Create_NoContentSet = @'
mutation createGroup($name: String!, $mrEnabled: Boolean!, $prefix: String!) {
computerGroupCreate(
input: {
name: $name
managementRightsEnabled: $mrEnabled
filter: {
sensor: { name: "Computer Name" }
op: STARTS_WITH
value: $prefix
}
}
) {
group { id name }
error { message }
}
}
'@
$Delete_ById = @'
mutation deleteComputerGroup($id: ID!) {
computerGroupDelete(ref: { id: $id }) {
id
error { message }
}
}
'@
# ---------- Mode selection & validation ----------
if ($Delete) {
if ([string]::IsNullOrWhiteSpace($Id)) {
Write-Warning "Missing -Id for deletion."
Show-ShortHelp
return
}
$Query = $Delete_ById
$Variables = @{ id = $Id }
$OpName = 'deleteComputerGroup'
}
else {
if ([string]::IsNullOrWhiteSpace($GroupName) -or [string]::IsNullOrWhiteSpace($Prefix)) {
Write-Warning "Missing -GroupName and/or -Prefix for creation."
Show-ShortHelp
return
}
$useCS = -not [string]::IsNullOrWhiteSpace($ContentSetName)
$Query = if ($useCS) { $Create_WithContentSet } else { $Create_NoContentSet }
$Variables = @{ name = $GroupName; mrEnabled = $MrEnabled; prefix = $Prefix }
if ($useCS) { $Variables.contentSetName = $ContentSetName }
$OpName = 'createGroup'
}
# ---------- Execute ----------
$bodyObj = @{ query = $Query; variables = $Variables; operationName = $OpName }
$bodyJson = $bodyObj | ConvertTo-Json -Depth 10
$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 }
}
# ---------- Output ----------
if ($Raw) { $resp | ConvertTo-Json -Depth 12; return }
if ($resp.errors) {
Write-Error ("GraphQL errors: " + (($resp.errors | ForEach-Object { $_.message }) -join '; '))
return
}
if ($Delete) {
$del = $resp.data.computerGroupDelete
if ($del.error -and $del.error.message) {
Write-Error ("Delete failed: {0}" -f $del.error.message)
} elseif ($del.id) {
Write-Host ("Deleted group ID: {0}" -f $del.id) -ForegroundColor Green
} else {
Write-Warning "No confirmation returned. Use -Raw to inspect the full response."
}
}
else {
$crt = $resp.data.computerGroupCreate
if ($crt.error -and $crt.error.message) {
Write-Error ("Create failed: {0}" -f $crt.error.message)
} elseif ($crt.group) {
Write-Host ("Created group: {0} (ID: {1})" -f $crt.group.name, $crt.group.id) -ForegroundColor Green
} else {
Write-Warning "No group returned. Use -Raw to inspect the full response."
}
}

View File

@@ -0,0 +1,89 @@
#requires -Version 7.0
<#
.SYNOPSIS
Initialize Tanium (Redden-TanREST) from config.json, then
export all roles whose name starts with a given prefix (default: CASH).
.PARAMETER Prefix
Role name prefix to match (prefix match, case-insensitive). Default: CASH.
.PARAMETER OutputFolder
Destination folder for JSON exports. Default: %TEMP%\RBAC
#>
param(
[string]$Prefix = 'CASH',
[string]$OutputFolder = "$env:TEMP\RBAC"
)
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
Import-Module Redden-TanREST -Force
# --- Load config
$ConfigPath = Join-Path $PSScriptRoot 'config.json'
if (-not (Test-Path $ConfigPath)) { throw "Configuration file not found: $ConfigPath" }
$config = Get-Content -Path $ConfigPath -Raw | ConvertFrom-Json
$TaniumUrl = ($config.TaniumUrl -replace '^https?://','').TrimEnd('/')
$TaniumTok = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumTok)) {
throw "Both TaniumUrl and TaniumApiToken must be provided in $ConfigPath."
}
# --- Prepare output
if (-not (Test-Path $OutputFolder)) { New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null }
# --- Temporary CLIXML for Initialize-TaniumSession
$TempXml = Join-Path $env:TEMP ("tanium-session-{0}.apicred" -f ([guid]::NewGuid().ToString('N')))
@{ baseURI = $TaniumUrl; token = ($TaniumTok | ConvertTo-SecureString -AsPlainText -Force) } |
Export-Clixml -Path $TempXml -Force
Write-Host "Initializing Tanium session..."
try {
Initialize-TaniumSession -PathToXML $TempXml | Out-Null
Write-Host "Session OK."
# ---------- GET roles starting with prefix ----------
# Prefer server-side regex; fall back to client-side if module/endpoint refuses inline (?i)
$regex = "(?i)^$([regex]::Escape($Prefix))"
$roles = $null
try { $roles = Get-Role -NameRegex $regex } catch { $roles = $null }
if (-not $roles) {
# fallback: pull all and filter locally
$roles = Get-Role -All | Where-Object { $_.name -match $regex -or $_.Name -match $regex }
}
if (-not $roles) {
Write-Warning "No roles found starting with '$Prefix'."
return
}
# ---------- Export each role ----------
$exported = @()
foreach ($r in @($roles)) {
$id = if ($r.PSObject.Properties.Name -contains 'id') { $r.id } elseif ($r.PSObject.Properties.Name -contains 'ID') { $r.ID } else { $null }
$name = if ($r.PSObject.Properties.Name -contains 'name') { $r.name } elseif ($r.PSObject.Properties.Name -contains 'Name') { $r.Name } else { $null }
if (-not $name) { continue }
try {
Export-RoleToJSON -RoleName $name -OutputFolder $OutputFolder -SkipReInitialize:$true -ErrorAction Stop
Write-Host ("✓ Exported: {0} (ID: {1})" -f $name, $id)
$exported += [pscustomobject]@{ Id=$id; Name=$name }
}
catch {
Write-Warning ("Export failed for role '{0}' (ID: {1}) — {2}" -f $name, $id, $_.Exception.Message)
}
}
if ($exported) {
Write-Host "`nSummary:"
$exported | Sort-Object Name | Format-Table Id,Name -AutoSize
Write-Host "`nJSON files in: $OutputFolder"
}
}
finally {
if (Test-Path $TempXml) {
Remove-Item $TempXml -Force -ErrorAction SilentlyContinue
Write-Host "Temporary CLIXML removed: $TempXml"
}
}

View File

@@ -0,0 +1,264 @@
#requires -Version 7.0
<#
.SYNOPSIS
Ensure a Tanium role by NAME: update if it exists, else create, then update from JSON.
.DESCRIPTION
- Accepts two JSON shapes:
(A) Custom: { "Description": "...", "Permissions": { "<Content Set>": { "privileges": { "#x":[ "...", ... ] } } }, "Display Name": "..." }
(B) Tanium export: { "object_list": { "content_set_roles": [ { name, description, content_set_role_privileges: [...] } ] } }
- Forces role name to -RoleName.
- Creates role if missing, then calls Update-TaniumRole.
.PARAMETER RoleName
Target role name to ensure (forced into JSON before update).
.PARAMETER PathToRoleJSON
Path to the role JSON file (custom or Tanium export).
.PARAMETER ContentSetOverride
Optional content set ID to force in Update-TaniumRole.
.PARAMETER ConfigPath
Path to config.json containing TaniumUrl and TaniumApiToken (default: alongside script).
.EXAMPLE
.\Ensure-TaniumRole.ps1 -RoleName "test - T2_TANIUM - N2 - Patch Operator" -PathToRoleJSON .\test.json
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string]$RoleName,
[Parameter(Mandatory = $true, Position = 1)]
[ValidateScript({
if (-not (Test-Path $_ -PathType Leaf)) { throw "File not found: $_" }
if ([IO.Path]::GetExtension($_) -notin '.json','.JSON') { throw "Not a .json file: $_" }
$true
})]
[string]$PathToRoleJSON,
[Parameter(Position = 2)]
[int]$ContentSetOverride = 0,
[string]$ConfigPath = (Join-Path $PSScriptRoot 'config.json')
)
begin {
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
if (-not (Get-Command -Name Update-TaniumRole -ErrorAction SilentlyContinue)) {
throw "Update-TaniumRole not found. Import the module that defines it before running this script."
}
# Load input JSON (any shape)
try {
$inObj = Get-Content -Path $PathToRoleJSON -Raw | ConvertFrom-Json -ErrorAction Stop
} catch {
throw "Invalid JSON in '$PathToRoleJSON': $($_.Exception.Message)"
}
function Convert-CustomRoleJsonToTaniumExport {
<#
Input (custom):
{
"Description": "...",
"Permissions": {
"ContentSetA": { "privileges": { "#patch": [ "patch X", "patch Y" ], "#admin":[ "read sensor" ] } },
"ContentSetB": { ... }
},
"Display Name": "..."
}
Output (export):
{
"comment":"Generated for Update-TaniumRole",
"version":2,
"object_list":{
"content_set_roles":[
{
"name":"<RoleName>",
"description":"<Description>",
"deny_flag":0,
"all_content_sets_flag":0,
"reserved_name":"",
"category":"custom",
"content_set_role_privileges":[
{ "content_set":{"name":"ContentSetA"}, "content_set_privilege":{"name":"patch X"}, "json_metadata":"" },
...
],
"metadata":[]
}
]
}
}
#>
param(
[Parameter(Mandatory)][object]$InputObject,
[Parameter(Mandatory)][string]$NameToForce
)
$desc = ''
if ($InputObject.'Description') { $desc = [string]$InputObject.'Description' }
elseif ($InputObject.description) { $desc = [string]$InputObject.description }
$privRows = New-Object System.Collections.Generic.List[object]
if ($InputObject.Permissions -is [object]) {
foreach ($csName in $InputObject.Permissions.PSObject.Properties.Name) {
$csBlock = $InputObject.Permissions.$csName
if ($csBlock -and $csBlock.privileges) {
foreach ($cat in $csBlock.privileges.PSObject.Properties.Name) {
$vals = $csBlock.privileges.$cat
if ($vals -is [System.Collections.IEnumerable]) {
foreach ($p in $vals) {
if ([string]::IsNullOrWhiteSpace($p)) { continue }
$privRows.Add([pscustomobject]@{
content_set = @{ name = $csName }
content_set_privilege = @{ name = [string]$p }
json_metadata = ""
})
}
}
}
}
}
}
# De-duplicate
$unique = $privRows |
Group-Object { "$($_.content_set.name)|$($_.content_set_privilege.name)" } |
ForEach-Object { $_.Group[0] }
return [pscustomobject]@{
comment = "Generated for Update-TaniumRole"
version = 2
object_list = @{
content_set_roles = @(
[pscustomobject]@{
name = $NameToForce
description = $desc
deny_flag = 0
all_content_sets_flag = 0
reserved_name = ""
category = "custom"
content_set_role_privileges = $unique
metadata = @()
}
)
}
}
}
# Normalize to Tanium export shape
$exportObj = $null
if ($inObj.object_list -and $inObj.object_list.content_set_roles) {
# Already export shape
$exportObj = $inObj
# enforce RoleName on the single role (or first)
$rolesArr = @($exportObj.object_list.content_set_roles)
if ($rolesArr.Count -eq 0) { throw "Export JSON contains no roles." }
if ($rolesArr.Count -gt 1) {
# Force only the first for safety; or throw if you want strict single-role files
throw "JSON contains multiple roles. Provide a single-role file or split it."
}
$exportObj.object_list.content_set_roles[0].name = $RoleName
}
else {
# Custom shape -> convert
if (-not $inObj.Permissions) {
throw "No content_set_roles[] found AND no Permissions{} block found in JSON."
}
$exportObj = Convert-CustomRoleJsonToTaniumExport -InputObject $inObj -NameToForce $RoleName
}
# Write temp JSON (don't touch original)
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
$tempJson = Join-Path $env:TEMP ("tanium-role-{0}-{1}.json" -f ($RoleName -replace '[^\w\-\. ]','_'), $ts)
$exportObj | ConvertTo-Json -Depth 100 | Set-Content -Path $tempJson -Encoding utf8
# Tanium API bootstrap
if (-not (Test-Path $ConfigPath -PathType Leaf)) {
throw "Config file not found: $ConfigPath"
}
$cfg = Get-Content -Path $ConfigPath -Raw | ConvertFrom-Json
$baseUrl = ($cfg.TaniumUrl -replace '/+$','')
if ($baseUrl -notmatch '^https?://') { $baseUrl = "https://$baseUrl" }
$headers = @{
'Accept' = 'application/json'
'Content-Type' = 'application/json'
}
if ($cfg.TaniumApiToken) {
$headers['X-Api-Token'] = $cfg.TaniumApiToken
$headers['session'] = $cfg.TaniumApiToken
}
function Invoke-TaniumApi {
param(
[Parameter(Mandatory)][ValidateSet('GET','POST','PATCH','PUT','DELETE')] [string]$Method,
[Parameter(Mandatory)][string]$Path,
[object]$Body = $null
)
$uri = '{0}{1}' -f $baseUrl, $Path
if ($Body -ne $null) {
Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -Body ($Body | ConvertTo-Json -Depth 20) -ErrorAction Stop
} else {
Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -ErrorAction Stop
}
}
function Get-TaniumUserRolesAll {
$resp = Invoke-TaniumApi -Method GET -Path '/api/v2/user_roles'
if ($resp.items) { return @($resp.items) }
if ($resp.data) { return @($resp.data) }
if ($resp.user_roles) { return @($resp.user_roles) }
if ($resp -is [System.Collections.IEnumerable]) { return @($resp) }
return @()
}
function Ensure-TaniumRoleIdByName {
param([Parameter(Mandatory)][string]$Name,[string]$Description = '')
$all = Get-TaniumUserRolesAll
$existing = $all | Where-Object { $_.name -eq $Name } | Select-Object -First 1
if ($existing -and $existing.id) { return [int]$existing.id }
# create
$body = @{
name = $Name
description = $Description
category = 'custom'
}
$resp = Invoke-TaniumApi -Method POST -Path '/api/v2/user_roles' -Body $body
if ($resp.id) { return [int]$resp.id }
if ($resp.item -and $resp.item.id) { return [int]$resp.item.id }
if ($resp.data -and $resp.data.id) { return [int]$resp.data.id }
$all2 = Get-TaniumUserRolesAll
$match = $all2 | Where-Object { $_.name -eq $Name } | Select-Object -First 1
if ($match -and $match.id) { return [int]$match.id }
throw "Role '$Name' created but ID could not be determined."
}
# Description from export for creation metadata
$desc = ''
try { $desc = [string]$exportObj.object_list.content_set_roles[0].description } catch {}
$script:TargetRoleId = Ensure-TaniumRoleIdByName -Name $RoleName -Description $desc
}
process {
try {
$params = @{
RoleIDs = @($script:TargetRoleId)
PathToRoleJSON = $tempJson
}
if ($ContentSetOverride -gt 0) { $params['ContentSetOverride'] = $ContentSetOverride }
Write-Host "Ensured role '$RoleName' (ID: $($script:TargetRoleId)). Applying JSON..." -ForegroundColor Cyan
$result = Update-TaniumRole @params
$result
}
finally {
if (Test-Path $tempJson) {
Remove-Item -Path $tempJson -Force -ErrorAction SilentlyContinue
}
}
}

View File

@@ -0,0 +1,219 @@
# List-ComputerGroups.ps1 — robust fallback + HTTP 400 handling + introspection
param(
[string]$Url,
[string]$Token,
[switch]$SkipCertCheck,
[int]$PageSize = 200,
[string]$NameLike,
[switch]$Raw,
[switch]$Grid,
[string]$ExportCsv,
[switch]$Introspect
)
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
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
$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 }
}
$gateway = Get-GatewayUri -HostLike $Url
$headers = @{ 'Content-Type' = 'application/json'; session = $Token }
# ---- Introspection (optional)
if ($Introspect) {
$IntrospectQuery = @'
query {
__type(name: "ComputerGroup") {
name
fields { name type { kind name ofType { kind name } } }
}
}
'@
$body = @{ query = $IntrospectQuery } | ConvertTo-Json -Depth 6
$resp = $null
$ps7 = ($PSVersionTable.PSVersion.Major -ge 7)
if ($ps7 -and $SkipCertCheck) {
$resp = Invoke-RestMethod -SkipCertificateCheck -Method Post -Uri $gateway -Headers $headers -Body $body
} else {
try { Enter-InsecureTls -Enable:$SkipCertCheck; $resp = Invoke-RestMethod -Method Post -Uri $gateway -Headers $headers -Body $body }
finally { Exit-InsecureTls }
}
if ($resp.errors) { Write-Error (($resp.errors | ForEach-Object message) -join '; '); return }
$resp.data.__type.fields | Select-Object name,@{n='type';e={$_.type.name ?? $_.type.ofType.name}} | Format-Table -AutoSize
return
}
# ---- Queries (order = safest → richer)
$Q_Basic = @'
query listComputerGroups($first: Int, $after: Cursor) {
computerGroups(first: $first, after: $after) {
edges { node { id name managementRightsEnabled } }
pageInfo { hasNextPage endCursor }
}
}
'@
$Q_ContentSet = @'
query listComputerGroups($first: Int, $after: Cursor) {
computerGroups(first: $first, after: $after) {
edges { node { id name managementRightsEnabled contentSet { name } } }
pageInfo { hasNextPage endCursor }
}
}
'@
$Q_ContentSetV2 = @'
query listComputerGroups($first: Int, $after: Cursor) {
computerGroups(first: $first, after: $after) {
edges { node { id name managementRightsEnabled contentSetV2 { name } } }
pageInfo { hasNextPage endCursor }
}
}
'@
$Q_Filter = @'
query listComputerGroups($first: Int, $after: Cursor) {
computerGroups(first: $first, after: $after) {
edges { node {
id name managementRightsEnabled
filter { memberOf { name } sensor { name } op value }
} }
pageInfo { hasNextPage endCursor }
}
}
'@
$queries = @($Q_Basic, $Q_ContentSet, $Q_ContentSetV2, $Q_Filter)
function Invoke-Gql {
param([string]$Query, [hashtable]$Vars, [string]$OpName)
$body = @{ query = $Query; variables = $Vars; operationName = $OpName } | ConvertTo-Json -Depth 10
$ps7 = ($PSVersionTable.PSVersion.Major -ge 7)
try {
if ($ps7 -and $SkipCertCheck) {
return Invoke-RestMethod -SkipCertificateCheck -Method Post -Uri $gateway -Headers $headers -ContentType 'application/json' -Body $body
} else {
Enter-InsecureTls -Enable:$SkipCertCheck
return Invoke-RestMethod -Method Post -Uri $gateway -Headers $headers -ContentType 'application/json' -Body $body
}
}
catch {
# If server returned HTTP 400 with a JSON GraphQL error, parse it and return so we can fallback.
$we = $_.Exception
if ($we.Response) {
try {
$sr = New-Object IO.StreamReader($we.Response.GetResponseStream())
$txt = $sr.ReadToEnd()
if ($txt) { return ($txt | ConvertFrom-Json) }
} catch { }
}
throw # non-GraphQL error -> bubble up
}
finally { Exit-InsecureTls }
}
# ---- Fetch with fallback chain
$allEdges = New-Object System.Collections.Generic.List[object]
$cursor = $null
$qIndex = 0
$opName = 'listComputerGroups'
while ($true) {
$vars = @{ first = $PageSize; after = $cursor }
$resp = Invoke-Gql -Query $queries[$qIndex] -Vars $vars -OpName $opName
if ($resp.errors) {
if ($qIndex -lt ($queries.Count - 1)) {
# restart from page 1 with next query
$qIndex++; $cursor = $null; $allEdges.Clear()
continue
} else {
throw ("GraphQL errors: " + (($resp.errors | ForEach-Object { $_.message }) -join '; '))
}
}
$edges = $resp.data.computerGroups.edges
if ($edges) { $allEdges.AddRange($edges) }
$pi = $resp.data.computerGroups.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 'No computer groups returned.'; return }
function Get-ContentSetName($n) {
if ($n.PSObject.Properties.Match('contentSet').Count -gt 0 -and $n.contentSet) { return $n.contentSet.name }
if ($n.PSObject.Properties.Match('contentSetV2').Count -gt 0 -and $n.contentSetV2) { return $n.contentSetV2.name }
return ''
}
function Get-FilterSummary($n) {
if (-not $n.PSObject.Properties.Match('filter')) { return '' }
$f = $n.filter; if ($null -eq $f) { return '' }
if ($f.memberOf -and $f.memberOf.name) { return ("memberOf: " + $f.memberOf.name) }
if ($f.sensor -and $f.sensor.name) { return ("sensor '" + $f.sensor.name + "' " + $f.op + " '" + $f.value + "'") }
return ''
}
$rows = foreach ($edge in $allEdges) {
$n = $edge.node
if ($NameLike -and -not ($n.name -match $NameLike)) { continue }
[pscustomobject]@{
Id = $n.id
Name = $n.name
ManagementRights = $n.managementRightsEnabled
ContentSet = (Get-ContentSetName $n)
Filter = (Get-FilterSummary $n)
}
}
if (-not $rows) { Write-Output 'No matching groups.'; 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 = "Computer Groups — {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

View File

@@ -0,0 +1,120 @@
<#
.SYNOPSIS
List Tanium **Content Sets** via the REST API, using Url/Token from `config.json` by default.
Supports optional filter by name (regex), OutGridView, and CSV export.
.USAGE
# Basic (reads config.json in the same folder)
./List-ContentSets.ps1
# Filter by name (regex), show grid and export CSV
./List-ContentSets.ps1 -NameLike '^Deploy' -Grid -ExportCsv C:\Temp\content_sets.csv
# Raw JSON
./List-ContentSets.ps1 -Raw
# Override Url/Token explicitly
./List-ContentSets.ps1 -Url tanium.pp.dktinfra.io -Token 'token-xxxx'
#>
param(
[string]$Url,
[string]$Token,
[switch]$SkipCertCheck,
[string]$NameLike,
[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 Content Sets ----------
# Common endpoint for content sets
$resp = Invoke-Tanium GET "/api/v2/content_sets" $null
# Tanium REST often returns either .data or .items
$contentSets = if ($resp.data) { $resp.data } elseif ($resp.items) { $resp.items } else { $resp }
if ($Raw) { $contentSets | ConvertTo-Json -Depth 12; return }
if (-not $contentSets) { Write-Output 'No content sets returned.'; return }
# ---------- Shape output ----------
$rows = foreach ($cs in $contentSets) {
if ($NameLike -and -not ($cs.name -match $NameLike)) { continue }
[pscustomobject]@{
Id = $cs.id
Name = $cs.name
Description = $cs.description
}
}
if (-not $rows) { Write-Output 'No matching content sets.'; 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 = "Content Sets — {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

106
API/RBAC_ListRole_Rest.ps1 Normal file
View File

@@ -0,0 +1,106 @@
<#
.SYNOPSIS
List Tanium Roles via REST (uses /api/v2/content_set_roles), reading Url/Token from config.json.
.USAGE
.\List-Roles.ps1
.\List-Roles.ps1 -NameLike '^Ops' -Grid -ExportCsv C:\Temp\roles.csv
.\List-Roles.ps1 -Raw
#>
param(
[string]$Url,
[string]$Token,
[switch]$SkipCertCheck,
[string]$NameLike,
[switch]$Raw,
[switch]$Grid,
[string]$ExportCsv
)
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
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 roles (correct endpoint) ----
$resp = Invoke-Tanium GET "/api/v2/content_set_roles" $null
# Most Tanium APIs return .data here
$roles = if ($resp.data) { $resp.data } elseif ($resp.items) { $resp.items } else { $resp }
if ($Raw) { $roles | ConvertTo-Json -Depth 12; return }
if (-not $roles) { Write-Output "No roles returned."; return }
# ---- Shape output ----
$rows = foreach ($r in $roles) {
if ($NameLike -and -not ($r.name -match $NameLike)) { continue }
[pscustomobject]@{
Id = $r.id
Name = $r.name
Description = $r.description
}
}
if (-not $rows) { Write-Output "No matching roles."; 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 = "Roles — {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

View File

@@ -0,0 +1,204 @@
#requires -Version 7.0
<#
.SYNOPSIS
Liste les Computer Groups (Management Rights) effectifs par UTILISATEUR et par USER GROUP,
en affichant les NOMS (pas les mrgroup_*).
- Auth depuis config.json (TaniumUrl, TaniumApiToken) via CLIXML -> Initialize-TaniumSession
- Déplie .group.id avec Get-MRGroupsFromGroup et mappe ID->Nom via Get-ManagementRightsGroup (fallback REST)
#>
# =========================
# Block 1 - Prérequis
# =========================
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
Import-Module Redden-TanREST -Force
# =========================
# Block 2 - Chargement config & init session
# =========================
$configPath = Join-Path $PSScriptRoot 'config.json'
$TempXml = Join-Path $env:TEMP 'tanium-session-tmp.apicred'
if (-not (Test-Path $configPath -PathType Leaf)) {
throw "Configuration file not found: $configPath (expected: TaniumUrl, TaniumApiToken)"
}
Write-Host "Reading configuration from: $configPath"
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
$TaniumUrl = $config.TaniumUrl
$TaniumToken = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumToken)) {
throw "Both TaniumUrl and TaniumApiToken must be provided."
}
if ($TaniumUrl -match '^https?://') {
$TaniumUrl = $TaniumUrl -replace '^https?://','' -replace '/+$',''
Write-Host "Normalized TaniumUrl to host: $TaniumUrl"
}
$ExportObject = @{
baseURI = $TaniumUrl
token = ($TaniumToken | ConvertTo-SecureString -AsPlainText -Force)
}
$ExportObject | Export-Clixml -Path $TempXml
Write-Host "Initializing Tanium session..."
Initialize-TaniumSession -PathToXML $TempXml
Write-Host "Tanium session initialized."
# =========================
# Block 3 - Index MR id -> nom (module puis REST fallback)
# =========================
$Script:MRIndex = @{}
try {
$allMR = Get-ManagementRightsGroup -All
} catch { $allMR = @() }
if (-not $allMR -or $allMR.Count -eq 0) {
# fallback REST
$baseUri = $TaniumUrl; if ($baseUri -notmatch '^https?://') { $baseUri = "https://$baseUri" }
$headers = @{
'Accept' = 'application/json'
'Content-Type' = 'application/json'
'X-Api-Token' = $TaniumToken
'session' = $TaniumToken
}
try {
$resp = Invoke-RestMethod -Method GET -Uri "$baseUri/api/v2/management_rights_groups?limit=5000" -Headers $headers
$allMR = @($resp.items ?? $resp.data ?? $resp)
} catch {
Write-Warning "Could not fetch Management Rights groups via REST: $($_.Exception.Message)"
$allMR = @()
}
}
foreach ($g in $allMR) {
if ($null -ne $g.id) { $Script:MRIndex[[int]$g.id] = [string]$g.name }
}
# =========================
# Block 4 - Helpers
# =========================
function Expand-NamedMRGroups {
<#
.SYNOPSIS
A partir d'un ID de groupe fusionné (.group.id), retourne les Computer Groups *nommés* (id/name).
Utilise Get-MRGroupsFromGroup puis mappe ID->Nom via $Script:MRIndex et filtre mrgroup_*.
#>
param([Parameter(Mandatory)][int]$MergedGroupID)
if ($MergedGroupID -lt 0) { return @() }
$items = @()
try { $items = @( Get-MRGroupsFromGroup -ID $MergedGroupID ) } catch { $items = @() }
$mapped = @()
foreach ($i in $items) {
$id = [int]$i.id
$name = [string]$i.name
if (-not $name -or $name -like 'mrgroup_*') { $name = $Script:MRIndex[$id] }
if ($name) {
$mapped += [pscustomobject]@{ id = $id; name = $name }
}
}
if ($mapped.Count -gt 0) {
$mapped = $mapped | Sort-Object id -Unique
}
return $mapped
}
function Get-UsersWithComputerGroups {
<#
.SYNOPSIS Récupère Users + leurs Computer Groups effectifs (nommés).
#>
$users = @()
try { $users = @(Get-User -All) }
catch { $users = @(Get-User -Summary) }
$rows = @()
foreach ($u in $users) {
$uid = [int]($u.id)
$name = [string]($u.display_name ?? $u.displayName ?? $u.name ?? $u.username)
$login = [string]($u.username)
$mergedId = 0
try { if ($u.group -and $u.group.id -ne $null) { $mergedId = [int]$u.group.id } } catch {}
$cgNamed = if ($mergedId -ge 0) { Expand-NamedMRGroups -MergedGroupID $mergedId } else { @() }
$cgNames = $cgNamed.name | Sort-Object -Unique
$cgIds = $cgNamed.id | Sort-Object -Unique
$rows += [pscustomobject]@{
id = $uid
name = $name
username = $login
mergedGroup = $mergedId
groupsCount = @($cgNames).Count
groupNames = ($cgNames -join ', ')
groupIDs = ($cgIds -join ', ')
}
}
if ($rows.Count -gt 0) { $rows = $rows | Sort-Object name }
return $rows
}
function Get-UserGroupsWithComputerGroups {
<#
.SYNOPSIS Récupère User Groups + leurs Computer Groups effectifs (nommés).
#>
$ugs = @(Get-UserGroup -All)
$rows = @()
foreach ($g in $ugs) {
$gid = [int]($g.id)
$gname = [string]($g.name)
$mergedId = 0
try { if ($g.group -and $g.group.id -ne $null) { $mergedId = [int]$g.group.id } } catch {}
$cgNamed = if ($mergedId -ge 0) { Expand-NamedMRGroups -MergedGroupID $mergedId } else { @() }
$cgNames = $cgNamed.name | Sort-Object -Unique
$cgIds = $cgNamed.id | Sort-Object -Unique
$rows += [pscustomobject]@{
id = $gid
name = $gname
mergedGroup = $mergedId
groupsCount = @($cgNames).Count
groupNames = ($cgNames -join ', ')
groupIDs = ($cgIds -join ', ')
}
}
if ($rows.Count -gt 0) { $rows = $rows | Sort-Object name }
return $rows
}
# =========================
# Block 5 - Exécution + affichage
# =========================
Write-Host ">> Gathering Users + Effective Computer Groups..." -ForegroundColor Cyan
$UsersWithCG = Get-UsersWithComputerGroups
Write-Host ">> Gathering User Groups + Effective Computer Groups..." -ForegroundColor Cyan
$UGsWithCG = Get-UserGroupsWithComputerGroups
$hasOGV = $null -ne (Get-Command Out-GridView -ErrorAction SilentlyContinue)
if ($hasOGV) {
$UsersWithCG | Out-GridView -Title 'Tanium Users Effective Computer Groups (by name)' -Wait
$UGsWithCG | Out-GridView -Title 'Tanium User Groups Effective Computer Groups (by name)' -Wait
} else {
Write-Host "`n=== USERS EFFECTIVE COMPUTER GROUPS ===" -ForegroundColor Green
$UsersWithCG | Format-Table id,name,username,groupsCount,groupNames -AutoSize
Write-Host "`n=== USER GROUPS EFFECTIVE COMPUTER GROUPS ===" -ForegroundColor Green
$UGsWithCG | Format-Table id,name,groupsCount,groupNames -AutoSize
Write-Host "`n(Out-GridView not available; showing tables instead)" -ForegroundColor Yellow
}
# =========================
# Block 6 - Cleanup
# =========================
if (Test-Path $TempXml) {
Remove-Item $TempXml -Force -ErrorAction SilentlyContinue
Write-Host "Temporary CLIXML removed: $TempXml"
}

View File

@@ -0,0 +1,220 @@
#requires -Version 7.0
<#
.SYNOPSIS
Affiche les rôles par UTILISATEUR (hors "Tanium Internal*") et/ou par GROUPE,
avec filtres -User et -Group. Sections affichées selon les filtres.
#>
[CmdletBinding()]
param(
[string[]]$User, # filtre sur display name / username (substring ou wildcard)
[string[]]$Group # filtre sur nom du groupe (substring ou wildcard)
)
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
# --- Auth depuis config.json ---
$ConfigPath = Join-Path $PSScriptRoot 'config.json'
if (-not (Test-Path $ConfigPath -PathType Leaf)) {
throw "Config introuvable: $ConfigPath (attendu: { `"TaniumUrl`", `"TaniumApiToken`" })"
}
$cfg = Get-Content -Path $ConfigPath -Raw | ConvertFrom-Json
$baseUrl = ($cfg.TaniumUrl -replace '/+$',''); if ($baseUrl -notmatch '^https?://') { $baseUrl = "https://$baseUrl" }
$headers = @{
'Accept' = 'application/json'
'Content-Type' = 'application/json'
}
if ($cfg.TaniumApiToken) {
$headers['X-Api-Token'] = $cfg.TaniumApiToken
$headers['session'] = $cfg.TaniumApiToken
}
function Invoke-TaniumApi {
param(
[Parameter(Mandatory)][ValidateSet('GET','POST','PATCH','PUT','DELETE')] [string]$Method,
[Parameter(Mandatory)][string]$Path,
[hashtable]$Query,
[object]$Body
)
$b = [System.UriBuilder]("$baseUrl$Path")
if ($Query) {
$nv = [System.Web.HttpUtility]::ParseQueryString([string]::Empty)
foreach ($k in $Query.Keys) { $nv[$k] = [string]$Query[$k] }
$b.Query = $nv.ToString()
}
$uri = $b.Uri.AbsoluteUri
if ($PSBoundParameters.ContainsKey('Body')) { Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -Body ($Body | ConvertTo-Json -Depth 20) }
else { Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers }
}
function Get-TaniumItems {
param([Parameter(Mandatory)][object]$Response)
if ($Response.items) { ,@($Response.items) }
elseif ($Response.data) { ,@($Response.data) }
elseif ($Response.users) { ,@($Response.users) }
elseif ($Response.user_groups) { ,@($Response.user_groups) }
else { ,@($Response) }
}
# helper: match substring/wildcard, case-insensitive
function Test-Match {
param([string]$Text, [string[]]$Patterns)
if (-not $Patterns -or $Patterns.Count -eq 0) { return $true }
foreach ($p in $Patterns) {
if ([string]::IsNullOrWhiteSpace($p)) { continue }
$pat = $p
if ($pat -notmatch '[\*\?\[\]]') { $pat = "*$pat*" } # ajoute wildcards si absent
if ($Text -like $pat -or $Text -like $pat.ToLower() -or $Text -like $pat.ToUpper()) { return $true }
}
return $false
}
# Déterminer quelles sections afficher
$hasUserFilter = $PSBoundParameters.ContainsKey('User') -and $User -and $User.Count -gt 0
$hasGroupFilter = $PSBoundParameters.ContainsKey('Group') -and $Group -and $Group.Count -gt 0
if ($hasUserFilter -and -not $hasGroupFilter) { $showUsers = $true; $showGroups = $false }
elseif ($hasGroupFilter -and -not $hasUserFilter) { $showUsers = $false; $showGroups = $true }
else { $showUsers = $true; $showGroups = $true } # aucun filtre OU les deux => montrer les deux
# --- Rôles (nécessaire pour libellés) ---
Write-Host ">> Récupère la liste des rôles..." -ForegroundColor Cyan
$rolesAll = Get-TaniumItems (Invoke-TaniumApi -Method GET -Path '/api/v2/content_set_roles' -Query @{ limit = 5000 })
$roleIndex = @{}; foreach ($r in $rolesAll) { if ($null -ne $r.id) { $roleIndex[[int]$r.id] = [string]$r.name } }
function Format-RoleLabel {
param([int]$Id, [string]$Name)
if ([string]::IsNullOrWhiteSpace($Name)) { $Name = $roleIndex[$Id] }
if ([string]::IsNullOrWhiteSpace($Name)) { "RoleID:$Id" } else { "RoleID:$Id ($Name)" }
}
# --- Utilisateurs (+ memberships) --------------------------------------------
$usersWithRoles = @()
if ($showUsers) {
Write-Host ">> Récupère les utilisateurs..." -ForegroundColor Cyan
$usersAll = Get-TaniumItems (Invoke-TaniumApi -Method GET -Path '/api/v2/users' -Query @{ limit = 5000 })
$users = $usersAll | Where-Object {
$disp = $_.display_name ?? $_.displayName ?? $_.name ?? $_.username
-not ($disp -like 'Tanium Internal*') -and (
(Test-Match -Text $disp -Patterns $User) -or (Test-Match -Text $_.username -Patterns $User)
)
}
Write-Host ">> Récupère les memberships (User<->Role)..." -ForegroundColor Cyan
$userRoleMships = Get-TaniumItems (Invoke-TaniumApi -Method GET -Path '/api/v2/content_set_role_memberships' -Query @{ limit = 50000 })
# Index users
$userIndex = @{}
foreach ($u in $users) {
$userIndex[[int]$u.id] = [pscustomobject]@{
ID = [int]$u.id
Name = $u.display_name ?? $u.displayName ?? $u.name ?? $u.username
Username = $u.username
Email = $u.email
}
}
# Rôles par user
$rolesByUser = @{}
foreach ($m in $userRoleMships) {
if ($null -eq $m.user -or $null -eq $m.content_set_role) { continue }
$uid = [int]$m.user.id
if (-not $userIndex.ContainsKey($uid)) { continue } # filtré/exclu
$rid = [int]$m.content_set_role.id
$rname = [string]$m.content_set_role.name
$label = Format-RoleLabel -Id $rid -Name $rname
if (-not $rolesByUser.ContainsKey($uid)) { $rolesByUser[$uid] = @() }
$rolesByUser[$uid] += $label
}
$usersWithRoles = $userIndex.GetEnumerator() |
ForEach-Object {
$uid = $_.Key
$info = $_.Value
$roles = @()
if ($rolesByUser.ContainsKey($uid)) {
$roles = $rolesByUser[$uid] | Where-Object { $_ } | Sort-Object -Unique
}
[pscustomobject]@{
ID = $info.ID
Nom = $info.Name
Username = $info.Username
RolesCount = $roles.Count
Roles = ($roles -join ', ')
}
} |
Sort-Object -Property Nom
}
# --- Groupes (+ memberships) --------------------------------------------------
$groupsWithRoles = @()
if ($showGroups) {
Write-Host ">> Récupère les groupes..." -ForegroundColor Cyan
$groupsAll = Get-TaniumItems (Invoke-TaniumApi -Method GET -Path '/api/v2/user_groups' -Query @{ limit = 5000 })
$groups = $groupsAll | Where-Object {
$gname = $_.name ?? $_.display_name ?? $_.displayName
Test-Match -Text $gname -Patterns $Group
}
Write-Host ">> Récupère les memberships (Group<->Role)..." -ForegroundColor Cyan
$groupRoleMships = Get-TaniumItems (Invoke-TaniumApi -Method GET -Path '/api/v2/content_set_user_group_role_memberships' -Query @{ limit = 50000 })
# Index groupes
$groupIndex = @{}
foreach ($g in $groups) {
$groupIndex[[int]$g.id] = [pscustomobject]@{
ID = [int]$g.id
Name = $g.name ?? $g.display_name ?? $g.displayName
}
}
# Rôles par groupe
$rolesByGroup = @{}
foreach ($m in $groupRoleMships) {
if ($null -eq $m.user_group -or $null -eq $m.content_set_role) { continue }
$gid = [int]$m.user_group.id
if (-not $groupIndex.ContainsKey($gid)) { continue } # filtré/exclu
$rid = [int]$m.content_set_role.id
$rname = [string]$m.content_set_role.name
$label = Format-RoleLabel -Id $rid -Name $rname
if (-not $rolesByGroup.ContainsKey($gid)) { $rolesByGroup[$gid] = @() }
$rolesByGroup[$gid] += $label
}
$groupsWithRoles = $groupIndex.GetEnumerator() |
ForEach-Object {
$gid = $_.Key
$g = $_.Value
$roles = @()
if ($rolesByGroup.ContainsKey($gid)) {
$roles = $rolesByGroup[$gid] | Where-Object { $_ } | Sort-Object -Unique
}
[pscustomobject]@{
ID = $g.ID
Groupe = $g.Name
RolesCount = $roles.Count
Roles = ($roles -join ', ')
}
} |
Sort-Object -Property Groupe
}
# --- Affichage conditionnel ---
if ($showUsers) {
Write-Host ""
Write-Host "=== ROLES PAR UTILISATEUR (hors 'Tanium Internal*') ===" -ForegroundColor Green
$usersWithRoles | Format-Table -Property ID,Nom,Username,RolesCount,Roles -AutoSize
}
if ($showGroups) {
Write-Host ""
Write-Host "=== ROLES PAR GROUPE ===" -ForegroundColor Green
$groupsWithRoles | Format-Table -Property ID,Groupe,RolesCount,Roles -AutoSize
}
# --- Sortie exploitable (ne saffiche que si tu ne lassignes pas) ---
[pscustomobject]@{
UsersWithRoles = $usersWithRoles
GroupsWithRoles = $groupsWithRoles
}

View File

@@ -0,0 +1,82 @@
#requires -Version 7.0
<#
.SYNOPSIS
List Tanium Deploy Software Packages using Get-DeploySoftware.
Reads URL/token from config.json, initializes the session, then queries by:
- All | ByName | ByID | ByVendor | ByCommand | NameRegex
and displays results in Out-GridView (fallback to console table).
#>
# =========================
# Block 1 - Prerequisites
# =========================
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
Import-Module Redden-TanREST -Force
# =========================
# Block 2 - Load config & init session
# =========================
$configPath = Join-Path $PSScriptRoot 'config.json'
$TempXml = Join-Path $env:TEMP 'tanium-session-tmp.apicred'
if (-not (Test-Path $configPath)) { throw "Configuration file not found: $configPath" }
Write-Host "Reading configuration from: $configPath"
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
$TaniumUrl = $config.TaniumUrl
$TaniumToken = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumToken)) {
throw "Both TaniumUrl and TaniumApiToken must be provided (config.json or environment variables)."
}
if ($TaniumUrl -match '^https?://') {
$TaniumUrl = $TaniumUrl -replace '^https?://','' -replace '/+$',''
Write-Host "Normalized TaniumUrl to host: $TaniumUrl"
}
$ExportObject = @{
baseURI = $TaniumUrl
token = ($TaniumToken | ConvertTo-SecureString -AsPlainText -Force)
}
$ExportObject | Export-Clixml -Path $TempXml
Write-Host "Initializing Tanium session..."
Initialize-TaniumSession -PathToXML $TempXml
Write-Host "Tanium session initialized."
# =========================
# Block 3 - Queries
# =========================
#Get-DeploySoftware -All | Out-GridView
#Get-Role -All | Out-GridView
#Get-UserAndGroupDetail
# -- Out-GridView available? (PS7 tip: Install-Module Microsoft.PowerShell.GraphicalTools)
$hasOGV = [bool](Get-Command Out-GridView -ErrorAction SilentlyContinue)
Write-Host "Fetching Users..." -ForegroundColor Cyan
$users = Get-User -All | Sort-Object displayName, username
if ($hasOGV) {
$users | Out-GridView -Title "Tanium Users (All)" -Wait
} else {
$users | Format-Table id, displayName, username, email -AutoSize
Write-Host "(Out-GridView not available; showing table output)" -ForegroundColor Yellow
}
Write-Host "Fetching User Groups..." -ForegroundColor Cyan
$groups = Get-UserGroup -All | Sort-Object name
if ($hasOGV) {
$groups | Out-GridView -Title "Tanium User Groups (All)" -Wait
} else {
$groups | Format-Table id, name, description -AutoSize
Write-Host "(Out-GridView not available; showing table output)" -ForegroundColor Yellow
}
# =========================
# Block 5 - Cleanup
# =========================
if (Test-Path $TempXml) {
Remove-Item $TempXml -Force -ErrorAction SilentlyContinue
Write-Host "Temporary CLIXML removed: $TempXml"
}

165
API/Sensor_List_Rest.ps1 Normal file
View File

@@ -0,0 +1,165 @@
<#
.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, OutGridView, 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

View File

@@ -0,0 +1,237 @@
<#
.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

View File

@@ -0,0 +1,119 @@
<#
.SYNOPSIS
Read URL/token from config.json, authenticate to Tanium Gateway (GraphQL),
then query "System Environment Variables" and print selected variables.
#>
# =========================
# Block 1 - Load config & build auth
# =========================
$ErrorActionPreference = 'Stop'
# config.json is next to this script
$configPath = Join-Path $PSScriptRoot 'config.json'
if (-not (Test-Path $configPath)) { throw "Configuration file not found: $configPath" }
Write-Host "Reading configuration from: $configPath"
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
$TaniumUrl = $config.TaniumUrl
$TaniumToken = $config.TaniumApiToken
if ([string]::IsNullOrWhiteSpace($TaniumUrl) -or [string]::IsNullOrWhiteSpace($TaniumToken)) {
throw "Both TaniumUrl and TaniumApiToken must be provided (config.json or environment variables)."
}
# Normalize host (no scheme/trailing slash)
if ($TaniumUrl -match '^https?://') { $TaniumUrl = $TaniumUrl -replace '^https?://','' -replace '/+$','' }
# Gateway GraphQL endpoint
$uri = "https://$TaniumUrl/plugin/products/gateway/graphql"
# HTTP headers with session token
$headers = @{
"Content-Type" = "application/json"
"session" = $TaniumToken
}
# =========================
# Block 2 - Quick auth check (GraphQL ping)
# =========================
try {
$pingBody = @{ query = 'query { __typename }' } | ConvertTo-Json
$pingResp = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $pingBody
if ($pingResp.errors) {
$msg = ($pingResp.errors | ForEach-Object { $_.message }) -join '; '
throw "GraphQL ping returned errors: $msg"
}
Write-Host "Authentication OK (Gateway reachable)."
}
catch {
throw "Authentication or connectivity failed: $($_.Exception.Message)"
}
# =========================
# Block 3 - Actual query
# =========================
# GraphQL variables
$variables = @{
count = 1 # adjust as needed
time = 10
maxAge = 600
}
# GraphQL query (columns/values only — no 'rows')
$query = @'
query getEndpoints($count: Int, $time: Int, $maxAge: Int) {
endpoints(source: { ts: { expectedCount: $count, stableWaitTime: $time, maxAge: $maxAge }}) {
edges {
node {
computerID
name
serialNumber
ipAddress
sensorReadings(sensors: [{ name: "System Environment Variables" }]) {
columns {
name
values
sensor { name }
}
}
}
}
}
}
'@
# Build request body
$body = @{ query = $query; variables = $variables } | ConvertTo-Json -Depth 20
# Call Gateway
$response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body
if ($response.errors) {
$msg = ($response.errors | ForEach-Object { $_.message }) -join '; '
throw "GraphQL returned errors: $msg"
}
# =========================
# Block 4 - Parse & print
# =========================
$endpoints = $response.data.endpoints.edges | ForEach-Object { $_.node }
# Env vars to keep (case-sensitive per your example)
$varsToKeep = @("PROCESSOR_LEVEL", "windir", "PROCESSOR_REVISION")
foreach ($endpoint in $endpoints) {
Write-Host "`n$($endpoint.name) [$($endpoint.ipAddress)] $($endpoint.serialNumber) :"
foreach ($reading in $endpoint.sensorReadings) {
foreach ($col in $reading.columns) {
foreach ($env in ($col.values | Where-Object { $_ })) {
if ($env -match "^(.*?)=(.*)$") {
$name = $matches[1]
$value = $matches[2]
if ($varsToKeep -contains $name) {
Write-Host " $name = $value"
}
}
}
}
}
}

View File

@@ -0,0 +1,219 @@
# List-ComputerGroups.ps1 — robust fallback + HTTP 400 handling + introspection
param(
[string]$Url,
[string]$Token,
[switch]$SkipCertCheck,
[int]$PageSize = 200,
[string]$NameLike,
[switch]$Raw,
[switch]$Grid,
[string]$ExportCsv,
[switch]$Introspect
)
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
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
$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 }
}
$gateway = Get-GatewayUri -HostLike $Url
$headers = @{ 'Content-Type' = 'application/json'; session = $Token }
# ---- Introspection (optional)
if ($Introspect) {
$IntrospectQuery = @'
query {
__type(name: "ComputerGroup") {
name
fields { name type { kind name ofType { kind name } } }
}
}
'@
$body = @{ query = $IntrospectQuery } | ConvertTo-Json -Depth 6
$resp = $null
$ps7 = ($PSVersionTable.PSVersion.Major -ge 7)
if ($ps7 -and $SkipCertCheck) {
$resp = Invoke-RestMethod -SkipCertificateCheck -Method Post -Uri $gateway -Headers $headers -Body $body
} else {
try { Enter-InsecureTls -Enable:$SkipCertCheck; $resp = Invoke-RestMethod -Method Post -Uri $gateway -Headers $headers -Body $body }
finally { Exit-InsecureTls }
}
if ($resp.errors) { Write-Error (($resp.errors | ForEach-Object message) -join '; '); return }
$resp.data.__type.fields | Select-Object name,@{n='type';e={$_.type.name ?? $_.type.ofType.name}} | Format-Table -AutoSize
return
}
# ---- Queries (order = safest → richer)
$Q_Basic = @'
query listComputerGroups($first: Int, $after: Cursor) {
computerGroups(first: $first, after: $after) {
edges { node { id name managementRightsEnabled } }
pageInfo { hasNextPage endCursor }
}
}
'@
$Q_ContentSet = @'
query listComputerGroups($first: Int, $after: Cursor) {
computerGroups(first: $first, after: $after) {
edges { node { id name managementRightsEnabled contentSet { name } } }
pageInfo { hasNextPage endCursor }
}
}
'@
$Q_ContentSetV2 = @'
query listComputerGroups($first: Int, $after: Cursor) {
computerGroups(first: $first, after: $after) {
edges { node { id name managementRightsEnabled contentSetV2 { name } } }
pageInfo { hasNextPage endCursor }
}
}
'@
$Q_Filter = @'
query listComputerGroups($first: Int, $after: Cursor) {
computerGroups(first: $first, after: $after) {
edges { node {
id name managementRightsEnabled
filter { memberOf { name } sensor { name } op value }
} }
pageInfo { hasNextPage endCursor }
}
}
'@
$queries = @($Q_Basic, $Q_ContentSet, $Q_ContentSetV2, $Q_Filter)
function Invoke-Gql {
param([string]$Query, [hashtable]$Vars, [string]$OpName)
$body = @{ query = $Query; variables = $Vars; operationName = $OpName } | ConvertTo-Json -Depth 10
$ps7 = ($PSVersionTable.PSVersion.Major -ge 7)
try {
if ($ps7 -and $SkipCertCheck) {
return Invoke-RestMethod -SkipCertificateCheck -Method Post -Uri $gateway -Headers $headers -ContentType 'application/json' -Body $body
} else {
Enter-InsecureTls -Enable:$SkipCertCheck
return Invoke-RestMethod -Method Post -Uri $gateway -Headers $headers -ContentType 'application/json' -Body $body
}
}
catch {
# If server returned HTTP 400 with a JSON GraphQL error, parse it and return so we can fallback.
$we = $_.Exception
if ($we.Response) {
try {
$sr = New-Object IO.StreamReader($we.Response.GetResponseStream())
$txt = $sr.ReadToEnd()
if ($txt) { return ($txt | ConvertFrom-Json) }
} catch { }
}
throw # non-GraphQL error -> bubble up
}
finally { Exit-InsecureTls }
}
# ---- Fetch with fallback chain
$allEdges = New-Object System.Collections.Generic.List[object]
$cursor = $null
$qIndex = 0
$opName = 'listComputerGroups'
while ($true) {
$vars = @{ first = $PageSize; after = $cursor }
$resp = Invoke-Gql -Query $queries[$qIndex] -Vars $vars -OpName $opName
if ($resp.errors) {
if ($qIndex -lt ($queries.Count - 1)) {
# restart from page 1 with next query
$qIndex++; $cursor = $null; $allEdges.Clear()
continue
} else {
throw ("GraphQL errors: " + (($resp.errors | ForEach-Object { $_.message }) -join '; '))
}
}
$edges = $resp.data.computerGroups.edges
if ($edges) { $allEdges.AddRange($edges) }
$pi = $resp.data.computerGroups.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 'No computer groups returned.'; return }
function Get-ContentSetName($n) {
if ($n.PSObject.Properties.Match('contentSet').Count -gt 0 -and $n.contentSet) { return $n.contentSet.name }
if ($n.PSObject.Properties.Match('contentSetV2').Count -gt 0 -and $n.contentSetV2) { return $n.contentSetV2.name }
return ''
}
function Get-FilterSummary($n) {
if (-not $n.PSObject.Properties.Match('filter')) { return '' }
$f = $n.filter; if ($null -eq $f) { return '' }
if ($f.memberOf -and $f.memberOf.name) { return ("memberOf: " + $f.memberOf.name) }
if ($f.sensor -and $f.sensor.name) { return ("sensor '" + $f.sensor.name + "' " + $f.op + " '" + $f.value + "'") }
return ''
}
$rows = foreach ($edge in $allEdges) {
$n = $edge.node
if ($NameLike -and -not ($n.name -match $NameLike)) { continue }
[pscustomobject]@{
Id = $n.id
Name = $n.name
ManagementRights = $n.managementRightsEnabled
ContentSet = (Get-ContentSetName $n)
Filter = (Get-FilterSummary $n)
}
}
if (-not $rows) { Write-Output 'No matching groups.'; 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 = "Computer Groups — {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

View File

@@ -0,0 +1,238 @@
<#
.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
}
}

89
API/test.ps1 Normal file
View File

@@ -0,0 +1,89 @@
<#
But : lire les infos endpoints à partir du cache (TDS) via GraphQL (Tanium Gateway)
#>
# --- Pré-requis : même init que ton script ---
$ErrorActionPreference = 'Stop'
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
Import-Module Redden-TanREST -Force
# Charger config.json + Initialiser la session (identique à ton script)
$configPath = Join-Path $PSScriptRoot 'config.json'
if (-not (Test-Path $configPath)) { throw "Configuration file not found: $configPath" }
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
$TaniumUrl = if ($config.TaniumUrl) { $config.TaniumUrl } else { $env:TANIUM_URL }
$TaniumApiToken = if ($config.TaniumApiToken) { $config.TaniumApiToken } else { $env:TANIUM_TOKEN }
if ($TaniumUrl -match '^https?://') { $TaniumUrl = $TaniumUrl -replace '^https?://','' -replace '/+$','' }
$TempXml = Join-Path $env:TEMP 'tanium-session-tmp.apicred'
@{
baseURI = $TaniumUrl
token = ($TaniumApiToken | ConvertTo-SecureString -AsPlainText -Force)
} | Export-Clixml -Path $TempXml
Initialize-TaniumSession -PathToXML $TempXml
# (Le point daccès GraphQL est /plugin/products/gateway/graphql côté Tanium; même auth que REST. :contentReference[oaicite:1]{index=1})
# --- Requête GraphQL (TDS/cached) ---
# NB : ici on filtre sur lOS qui "contient Windows".
# Si le champ diffère dans ton schéma (ex: operatingSystemName), adapte 'path' et/ou les champs retournés.
$query = @'
query ($first:Int, $after:Cursor, $os:String!) {
endpoints(
first: $first
after: $after
# Filtre simple : champ "operatingSystem" qui contient la valeur $os
filter: { path: "operatingSystem", value: $os, op: CONTAINS }
) {
totalRecords
edges {
node {
id
name
ipAddress
serialNumber
operatingSystem
eidLastSeen
}
}
pageInfo { hasNextPage endCursor }
}
}
'@
# Variables initiales (page de 500 éléments)
$variables = @{
first = 500
after = $null
os = 'Windows'
}
# --- Exécution + pagination ---
$all = New-Object System.Collections.Generic.List[object]
do {
# Invoke-TaniumGateway exécute la requête/variables avec ta session Redden-TanREST. :contentReference[oaicite:2]{index=2}
$out = Invoke-TaniumGateway -Query $query -Variables $variables
$page = $out.data.endpoints
foreach ($edge in $page.edges) {
$n = $edge.node
$all.Add([pscustomobject]@{
Id = $n.id
Name = $n.name
IP = $n.ipAddress
Serial = $n.serialNumber
OS = $n.operatingSystem
LastSeen = $n.eidLastSeen
})
}
$variables.after = $page.pageInfo.endCursor
} while ($page.pageInfo.hasNextPage)
# Affichage (Grid)
$all | Out-GridView -Title 'Windows endpoints (TDS cached via GraphQL)'
# --- Nettoyage ---
Remove-Item $TempXml -Force -ErrorAction SilentlyContinue