#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": { "": { "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":"", "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 } } }