Auto-commit: 2025-10-31 09:03:38
This commit is contained in:
@@ -1,106 +0,0 @@
|
||||
<#
|
||||
.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
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
<#
|
||||
.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)"
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
<#
|
||||
.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 l’ID 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, d’autres 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
|
||||
@@ -1,59 +0,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 - 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"
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
<#
|
||||
.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)"
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
<#
|
||||
.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)"
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
<#
|
||||
.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)"
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
<#
|
||||
.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."
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
#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"
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
#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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
# 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
|
||||
@@ -1,120 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
List Tanium **Content Sets** via the REST API, using Url/Token from `config.json` by default.
|
||||
Supports optional filter by name (regex), Out‑GridView, 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
|
||||
@@ -1,106 +0,0 @@
|
||||
<#
|
||||
.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
|
||||
@@ -1,204 +0,0 @@
|
||||
#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"
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
#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 s’affiche que si tu ne l’assignes pas) ---
|
||||
[pscustomobject]@{
|
||||
UsersWithRoles = $usersWithRoles
|
||||
GroupsWithRoles = $groupsWithRoles
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#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"
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
List Tanium **Sensors** via the REST API, using Url/Token from `config.json` by default.
|
||||
Shows: Id, Name, Content Set, Category, Parameters?, CacheSeconds (if exposed).
|
||||
Supports filters by name/content set/category, Out‑GridView, and CSV export.
|
||||
|
||||
.USAGE
|
||||
# Basic (reads config.json in the same folder)
|
||||
./List-Sensors.ps1
|
||||
|
||||
# Filter by name/content set/category (regex), show grid and export CSV
|
||||
./List-Sensors.ps1 -NameLike '^BIOS' -ContentSetLike 'Deploy' -CategoryLike 'Hardware' -Grid -ExportCsv C:\Temp\sensors.csv
|
||||
|
||||
# Raw JSON
|
||||
./List-Sensors.ps1 -Raw
|
||||
|
||||
# Override Url/Token explicitly
|
||||
./List-Sensors.ps1 -Url tanium.pp.dktinfra.io -Token 'token-xxxx'
|
||||
#>
|
||||
|
||||
param(
|
||||
[string]$Url,
|
||||
[string]$Token,
|
||||
[switch]$SkipCertCheck,
|
||||
|
||||
[string]$NameLike,
|
||||
[string]$ContentSetLike,
|
||||
[string]$CategoryLike,
|
||||
|
||||
[switch]$Raw,
|
||||
[switch]$Grid,
|
||||
[string]$ExportCsv
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
|
||||
|
||||
# ---------- Helpers ----------
|
||||
function New-BaseUri([string]$HostLike) {
|
||||
$h = $HostLike.Trim()
|
||||
if ($h -match '^https?://') { $h = $h -replace '^https?://','' }
|
||||
$h = $h.TrimEnd('/')
|
||||
if ([string]::IsNullOrWhiteSpace($h)) { throw 'TaniumUrl empty after normalization.' }
|
||||
"https://$h"
|
||||
}
|
||||
|
||||
# TLS bypass for Windows PowerShell 5.1
|
||||
$script:__oldCb = $null
|
||||
function Enter-InsecureTls { param([switch]$Enable)
|
||||
if (-not $Enable) { return }
|
||||
$script:__oldCb = [System.Net.ServicePointManager]::ServerCertificateValidationCallback
|
||||
$cb = [System.Net.Security.RemoteCertificateValidationCallback]{ param($s,$c,$ch,$e) $true }
|
||||
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = $cb
|
||||
}
|
||||
function Exit-InsecureTls {
|
||||
if ($script:__oldCb -ne $null) {
|
||||
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = $script:__oldCb
|
||||
$script:__oldCb = $null
|
||||
}
|
||||
}
|
||||
|
||||
# ---------- Load config if needed ----------
|
||||
$BaseDir = if ($PSScriptRoot) { $PSScriptRoot } else { $pwd.Path }
|
||||
$configPath = Join-Path $BaseDir 'config.json'
|
||||
if (-not $Url -or -not $Token) {
|
||||
if (-not (Test-Path $configPath)) { throw "Missing -Url/-Token and config.json not found: $configPath" }
|
||||
$cfg = Get-Content -Path $configPath -Raw | ConvertFrom-Json
|
||||
if (-not $Url) { $Url = [string]$cfg.TaniumUrl }
|
||||
if (-not $Token) { $Token = [string]$cfg.TaniumApiToken }
|
||||
if (-not $PSBoundParameters.ContainsKey('SkipCertCheck')) { $SkipCertCheck = [bool]$cfg.SkipCertificateCheck }
|
||||
}
|
||||
|
||||
$Base = New-BaseUri $Url
|
||||
$Headers = @{ session = $Token; 'Content-Type' = 'application/json' }
|
||||
|
||||
function Invoke-Tanium([string]$Method,[string]$Path,[object]$BodyObj) {
|
||||
$uri = if ($Path -match '^https?://') { $Path } else { "$Base$Path" }
|
||||
$body = if ($null -ne $BodyObj) { $BodyObj | ConvertTo-Json -Depth 10 } else { $null }
|
||||
$ps7 = ($PSVersionTable.PSVersion.Major -ge 7)
|
||||
if ($ps7 -and $SkipCertCheck) {
|
||||
return Invoke-RestMethod -SkipCertificateCheck -Method $Method -Uri $uri -Headers $Headers -Body $body
|
||||
} else {
|
||||
try { Enter-InsecureTls -Enable:$SkipCertCheck; return Invoke-RestMethod -Method $Method -Uri $uri -Headers $Headers -Body $body }
|
||||
finally { Exit-InsecureTls }
|
||||
}
|
||||
}
|
||||
|
||||
# ---------- Fetch Sensors + Content Sets (for names) ----------
|
||||
$resp = Invoke-Tanium GET "/api/v2/sensors" $null
|
||||
$sensors = if ($resp.data) { $resp.data } elseif ($resp.items) { $resp.items } else { $resp }
|
||||
|
||||
$csResp = Invoke-Tanium GET "/api/v2/content_sets" $null
|
||||
$contentSets = if ($csResp.data) { $csResp.data } elseif ($csResp.items) { $csResp.items } else { $csResp }
|
||||
$csById = @{}
|
||||
foreach ($cs in $contentSets) { if ($cs.id -ne $null) { $csById[$cs.id] = $cs } }
|
||||
|
||||
if ($Raw) {
|
||||
[pscustomobject]@{ sensors = $sensors; contentSets = $contentSets } | ConvertTo-Json -Depth 14
|
||||
return
|
||||
}
|
||||
|
||||
if (-not $sensors) { Write-Output 'No sensors returned.'; return }
|
||||
|
||||
# ---------- Helpers to read maybe-present fields ----------
|
||||
function TryField($obj, $path) {
|
||||
try {
|
||||
$val = $obj
|
||||
foreach ($p in ($path -split '\.')) {
|
||||
if ($null -eq $val) { return $null }
|
||||
$pp = $val.PSObject.Properties[$p]
|
||||
if (-not $pp) { return $null }
|
||||
$val = $pp.Value
|
||||
}
|
||||
return $val
|
||||
} catch { return $null }
|
||||
}
|
||||
|
||||
# ---------- Shape output ----------
|
||||
$rows = foreach ($s in $sensors) {
|
||||
$name = TryField $s 'name'
|
||||
if ($NameLike -and $name -and -not ($name -match $NameLike)) { continue }
|
||||
|
||||
# content set: could be nested or just id
|
||||
$csName = ''
|
||||
$csId = TryField $s 'content_set.id'
|
||||
if ($null -eq $csId) { $csId = TryField $s 'content_set_id' }
|
||||
if ($null -ne $csId -and $csById.ContainsKey($csId)) { $csName = $csById[$csId].name }
|
||||
if ($ContentSetLike -and $csName -and -not ($csName -match $ContentSetLike)) { continue }
|
||||
|
||||
$category = TryField $s 'category'
|
||||
if ($CategoryLike -and $category -and -not ($category -match $CategoryLike)) { continue }
|
||||
|
||||
$hasParams = $false
|
||||
if (TryField $s 'parameters') { $hasParams = $true }
|
||||
elseif (TryField $s 'parameter_definitions') { $hasParams = $true }
|
||||
elseif (TryField $s 'parameterDefinitions') { $hasParams = $true }
|
||||
|
||||
$cacheSec = TryField $s 'expiration_seconds'
|
||||
if ($null -eq $cacheSec) { $cacheSec = TryField $s 'cache_seconds' }
|
||||
|
||||
[pscustomobject]@{
|
||||
Id = TryField $s 'id'
|
||||
Name = $name
|
||||
ContentSet = $csName
|
||||
Category = $category
|
||||
Parameters = $hasParams
|
||||
CacheSeconds = $cacheSec
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $rows) { Write-Output 'No matching sensors.'; return }
|
||||
$rowsSorted = $rows | Sort-Object Name
|
||||
|
||||
if ($ExportCsv) {
|
||||
try { $rowsSorted | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $ExportCsv; Write-Host "Exported to: $ExportCsv" -ForegroundColor Green }
|
||||
catch { Write-Warning "CSV export failed: $($_.Exception.Message)" }
|
||||
}
|
||||
|
||||
if ($Grid) {
|
||||
$title = "Sensors — {0} item(s)" -f ($rowsSorted.Count)
|
||||
if (Get-Command Out-GridView -ErrorAction SilentlyContinue) { $rowsSorted | Out-GridView -Title $title; return }
|
||||
else { Write-Warning "Out-GridView not available. On PS7+: Install-Module Microsoft.PowerShell.GraphicalTools" }
|
||||
}
|
||||
|
||||
$rowsSorted | Format-Table -AutoSize
|
||||
@@ -1,237 +0,0 @@
|
||||
<#
|
||||
.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
|
||||
@@ -1,119 +0,0 @@
|
||||
<#
|
||||
.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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
# 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
|
||||
@@ -1,238 +0,0 @@
|
||||
<#
|
||||
.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
89
API/test.ps1
@@ -1,89 +0,0 @@
|
||||
<#
|
||||
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 d’accè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 l’OS 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
|
||||
@@ -1,41 +0,0 @@
|
||||
# 🔍 Regex Filter for Even-Numbered Hostnames
|
||||
|
||||
This regex is used in **Tanium filters** to match hostnames ending with an **even digit** in their first label.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Even-Numbered Hostnames (FQDN)
|
||||
|
||||
```regex
|
||||
^[^.]*[02468]\..+
|
||||
```
|
||||
^[^.]* → matches the first label (hostname part before the first dot).
|
||||
[02468] → requires it to end with an even digit.
|
||||
\..+ → ensures there is a domain suffix (must be a FQDN).
|
||||
|
||||
Examples
|
||||
- pc100.dkcorp.net ✅
|
||||
- pc8.lab.example.org ✅
|
||||
- pc101.sud.dkcorp.net ❌ (ends with odd digit 1)
|
||||
- pc8 ❌ (not a FQDN, no dot)
|
||||
|
||||
## ✅ Even-Numbered Hostnames (Optional FQDN)
|
||||
|
||||
```regex
|
||||
^[^.]*[02468](?:\..+)?$
|
||||
```
|
||||
^[^.]* → matches the first label (hostname part before the first dot).
|
||||
[02468] → requires it to end with an even digit.
|
||||
(?:\..+)? → optional dot + domain (accepts both hostname and FQDN)
|
||||
|
||||
✅ Examples
|
||||
Matches:
|
||||
- pc8
|
||||
- pc100
|
||||
- pc8.lab
|
||||
- pc100.dkcorp.net
|
||||
|
||||
❌ Does not match:
|
||||
- pc101 (ends with odd digit)
|
||||
- pc8. (nothing after the dot)
|
||||
- pc.8 (even digit not at the end of first label)
|
||||
@@ -1,22 +0,0 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$isOS64 = [Environment]::Is64BitOperatingSystem
|
||||
$isProc64 = [Environment]::Is64BitProcess
|
||||
|
||||
$KeyPS = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp'
|
||||
$KeyRel = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp'
|
||||
$Name = 'DisableWpad'
|
||||
|
||||
if ($isOS64 -and -not $isProc64) {
|
||||
# 32-bit PowerShell on 64-bit OS -> write to 64-bit registry view
|
||||
$bk = [Microsoft.Win32.RegistryKey]::OpenBaseKey(
|
||||
[Microsoft.Win32.RegistryHive]::LocalMachine,
|
||||
[Microsoft.Win32.RegistryView]::Registry64
|
||||
)
|
||||
$k = $bk.CreateSubKey($KeyRel)
|
||||
$k.SetValue($Name, 1, [Microsoft.Win32.RegistryValueKind]::DWord)
|
||||
$k.Close()
|
||||
} else {
|
||||
if (-not (Test-Path -LiteralPath $KeyPS)) { New-Item -Path $KeyPS -Force | Out-Null }
|
||||
New-ItemProperty -Path $KeyPS -Name $Name -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
# 🧩 HKLM Registry Write (64-bit)
|
||||
|
||||
## 🔧 What it does
|
||||
- Opens **HKLM** in the **64-bit registry view**.
|
||||
- Creates/opens subkey
|
||||
- Sets value
|
||||
|
||||
## ✅ Prerequisites
|
||||
- Run as **Administrator**.
|
||||
- Writes to the **64-bit** hive; use `Registry32` if you need the 32-bit view.
|
||||
@@ -1,37 +0,0 @@
|
||||
#requires -version 5.1
|
||||
# Forced shutdown in 30 seconds with on-screen message.
|
||||
# Works from 32-bit PowerShell on 64-bit Windows. Run as Administrator.
|
||||
|
||||
$Message = 'Shutdown by Tanium'
|
||||
$TimeoutSeconds = 30
|
||||
|
||||
# Admin check
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {
|
||||
Write-Error 'Run this script as Administrator.'
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Pick correct shutdown.exe (Sysnative when 32-bit PS on 64-bit OS)
|
||||
function Get-ShutdownExePath {
|
||||
$sysnative = Join-Path $env:WINDIR 'Sysnative\shutdown.exe'
|
||||
$system32 = Join-Path $env:WINDIR 'System32\shutdown.exe'
|
||||
if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess -and (Test-Path $sysnative)) {
|
||||
return $sysnative
|
||||
} else {
|
||||
return $system32
|
||||
}
|
||||
}
|
||||
$exe = Get-ShutdownExePath
|
||||
|
||||
# Optional trace in Event Log
|
||||
try {
|
||||
$src = 'Tanium-Shutdown-PS'
|
||||
if (-not [System.Diagnostics.EventLog]::SourceExists($src)) {
|
||||
New-EventLog -LogName Application -Source $src -ErrorAction SilentlyContinue
|
||||
}
|
||||
Write-EventLog -LogName Application -Source $src -EntryType Information -EventId 10011 -Message $Message
|
||||
} catch {}
|
||||
|
||||
# Schedule forced shutdown with 30s countdown and message
|
||||
& $exe /s /f /t $TimeoutSeconds /c $Message
|
||||
@@ -1,5 +0,0 @@
|
||||
# Documentation
|
||||
|
||||
For the full documentation, please visit the link below:
|
||||
|
||||
[Windows Provisionning With Tanium](https://blog.wuibaille.fr/2024/09/windows-provisionning-with-tanium/)
|
||||
@@ -1,114 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||
<settings pass="generalize">
|
||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<DoNotCleanTaskBar>true</DoNotCleanTaskBar>
|
||||
</component>
|
||||
</settings>
|
||||
<settings pass="specialize">
|
||||
<component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||
<Identification>
|
||||
<JoinWorkgroup>MyWorkgroup</JoinWorkgroup>
|
||||
</Identification>
|
||||
</component>
|
||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||
<ComputerName>nomPC</ComputerName>
|
||||
<RegisteredOrganization>MonOrganisation</RegisteredOrganization>
|
||||
<RegisteredOwner>MonOrganisation</RegisteredOwner>
|
||||
<DoNotCleanTaskBar>true</DoNotCleanTaskBar>
|
||||
<TimeZone>Romance Standard Time</TimeZone>
|
||||
<ProductKey>M7XTQ-FN8P6-TTKYV-9D4CC-J462D</ProductKey>
|
||||
</component>
|
||||
<component name="Microsoft-Windows-IE-InternetExplorer" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Home_Page>www.google.fr</Home_Page>
|
||||
</component>
|
||||
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<RunSynchronous>
|
||||
<RunSynchronousCommand wcm:action="add">
|
||||
<Description>EnableAdmin</Description>
|
||||
<Order>1</Order>
|
||||
<Path>cmd /c net user Administrator /active:yes</Path>
|
||||
</RunSynchronousCommand>
|
||||
<RunSynchronousCommand wcm:action="add">
|
||||
<Description>UnfilterAdministratorToken</Description>
|
||||
<Order>2</Order>
|
||||
<Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v FilterAdministratorToken /t REG_DWORD /d 0 /f</Path>
|
||||
</RunSynchronousCommand>
|
||||
<RunSynchronousCommand wcm:action="add">
|
||||
<Description>disable user account page</Description>
|
||||
<Order>3</Order>
|
||||
<Path>reg add HKLM\Software\Microsoft\Windows\CurrentVersion\Setup\OOBE /v UnattendCreatedUser /t REG_DWORD /d 1 /f</Path>
|
||||
</RunSynchronousCommand>
|
||||
<RunSynchronousCommand wcm:action="add">
|
||||
<Description>disable async RunOnce</Description>
|
||||
<Order>4</Order>
|
||||
<Path>reg add HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer /v AsyncRunOnce /t REG_DWORD /d 0 /f</Path>
|
||||
</RunSynchronousCommand>
|
||||
</RunSynchronous>
|
||||
</component>
|
||||
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<InputLocale>fr-fr;040c:0000040c</InputLocale>
|
||||
<SystemLocale>en-us</SystemLocale>
|
||||
<UILanguage>en-us</UILanguage>
|
||||
<UserLocale>en-us</UserLocale>
|
||||
</component>
|
||||
<component name="Microsoft-Windows-TapiSetup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<TapiConfigured>0</TapiConfigured>
|
||||
<TapiUnattendLocation>
|
||||
<AreaCode>""</AreaCode>
|
||||
<CountryOrRegion>1</CountryOrRegion>
|
||||
<LongDistanceAccess>9</LongDistanceAccess>
|
||||
<OutsideAccess>9</OutsideAccess>
|
||||
<PulseOrToneDialing>1</PulseOrToneDialing>
|
||||
<DisableCallWaiting>""</DisableCallWaiting>
|
||||
<InternationalCarrierCode>""</InternationalCarrierCode>
|
||||
<LongDistanceCarrierCode>""</LongDistanceCarrierCode>
|
||||
<Name>Default</Name>
|
||||
</TapiUnattendLocation>
|
||||
</component>
|
||||
<component name="Microsoft-Windows-SystemRestore-Main" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<DisableSR>1</DisableSR>
|
||||
</component>
|
||||
</settings>
|
||||
<settings pass="oobeSystem">
|
||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||
<UserAccounts>
|
||||
<AdministratorPassword>
|
||||
<Value>Password1</Value>
|
||||
<PlainText>true</PlainText>
|
||||
</AdministratorPassword>
|
||||
</UserAccounts>
|
||||
<AutoLogon>
|
||||
<Enabled>true</Enabled>
|
||||
<Username>Administrator</Username>
|
||||
<Domain>.</Domain>
|
||||
<Password>
|
||||
<Value>Password1</Value>
|
||||
<PlainText>true</PlainText>
|
||||
</Password>
|
||||
<LogonCount>1</LogonCount>
|
||||
</AutoLogon>
|
||||
<Display></Display>
|
||||
<OOBE>
|
||||
<HideEULAPage>true</HideEULAPage>
|
||||
<NetworkLocation>Work</NetworkLocation>
|
||||
<ProtectYourPC>1</ProtectYourPC>
|
||||
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
||||
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
||||
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
||||
<SkipMachineOOBE>true</SkipMachineOOBE>
|
||||
<SkipUserOOBE>true</SkipUserOOBE>
|
||||
</OOBE>
|
||||
<RegisteredOrganization>MonOrganisation</RegisteredOrganization>
|
||||
<RegisteredOwner>MonOrganisation</RegisteredOwner>
|
||||
<TimeZone>Romance Standard Time</TimeZone>
|
||||
</component>
|
||||
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<InputLocale>fr-fr;040c:0000040c</InputLocale>
|
||||
<SystemLocale>en-us</SystemLocale>
|
||||
<UILanguage>en-us</UILanguage>
|
||||
<UserLocale>en-us</UserLocale>
|
||||
</component>
|
||||
</settings>
|
||||
<cpi:offlineImage cpi:source="" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
|
||||
</unattend>
|
||||
Reference in New Issue
Block a user