From e904fa053b7ebfce4bfd2b6e6496875c90916983 Mon Sep 17 00:00:00 2001 From: Thomas Obarowski Date: Fri, 19 Dec 2025 20:38:37 -0500 Subject: [PATCH 1/6] (feat) Added parameter sets, created function to generate CSV template, built initial function to read CSV. --- .../Pin Assets To Clusters/.gitignore | 2 + .../Pin-AssetsToClusters.ps1 | 128 ++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore b/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore new file mode 100644 index 0000000..b0972c0 --- /dev/null +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore @@ -0,0 +1,2 @@ +*.csv +.env diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 b/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 index e69de29..fbf0073 100644 --- a/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 @@ -0,0 +1,128 @@ +#requires -Modules ZeroNetworks +#requires -Version 7.0 + +[CmdletBinding(DefaultParameterSetName = "ByAssetId")] +param( + # Shared parameter for sets that require authentication + [Parameter(ParameterSetName = "ByAssetId", Mandatory = $true)] + [Parameter(ParameterSetName = "ListDeploymentClusters", Mandatory = $true)] + [Parameter(ParameterSetName = "ByCsvPath", Mandatory = $true)] + [string]$ApiKey, + + # ParameterSet 1: Pin by Asset ID and Deployment Cluster ID + [Parameter(ParameterSetName = "ByAssetId", Mandatory = $true)] + [string[]]$AssetId, + + [Parameter(ParameterSetName = "ByAssetId", Mandatory = $true)] + [string]$DeploymentClusterId, + + # Shared switch parameter for unpinning (available in ByAssetId and ByCsvPath sets) + [Parameter(ParameterSetName = "ByAssetId")] + [Parameter(ParameterSetName = "ByCsvPath")] + [switch]$Unpin, + + # ParameterSet 2: List Deployment Clusters + [Parameter(ParameterSetName = "ListDeploymentClusters", Mandatory = $true)] + [switch]$ListDeploymentClusters, + + # ParameterSet 3: Pin from CSV file + [Parameter(ParameterSetName = "ByCsvPath", Mandatory = $true)] + [string]$CsvPath, + + # ParameterSet 4: Export CSV Template + [Parameter(ParameterSetName = "ExportCsvTemplate", Mandatory = $true)] + [switch]$ExportCsvTemplate +) + + +function Export-CsvTemplate { + $template = [PSCustomObject]@{ + AssetName = $null + AssetId = $null + DeploymentClusterId = $null + } + $template | Export-Csv -Path ".\pin-assets-to-clusters-template.csv" -NoTypeInformation + Write-Output "CSV Template exported to .\pin-assets-to-clusters-template.csv" + Write-Output "Please fill in AT LEAST the AssetId and DeploymentClusterId columsn, and then run the script again with the -CsvPath parameter to pin the assets to the clusters." + Write-Output "Example: .\Pin-AssetsToClusters.ps1 -CsvPath '.\pin-assets-to-clusters.csv' -ApiKey 'your-api-key'" + Exit 0 +} + +function Get-CsvData { + # Check if CSV file exists + if (-not (Test-Path -Path $CsvPath)) { + Write-Error "CSV file not found: $CsvPath" + exit 1 + } + + # Read CSV file into an array of PSCustomObjects + try { + $csvData = @(Import-Csv -Path $CsvPath) + } + catch { + Write-Error "Failed to read CSV file: $_" + exit 1 + } + + # Validate that CSV has data + if ($csvData.Count -eq 0) { + Write-Error "CSV file is empty or contains no data rows." + exit 1 + } + + # Validate header row has required columns + $requiredColumns = @('AssetId', 'DeploymentClusterId') + $firstRow = $csvData[0] + $actualColumns = $firstRow.PSObject.Properties.Name + $missingColumns = @() + + foreach ($column in $requiredColumns) { + if ($actualColumns -notcontains $column) { + $missingColumns += $column + } + } + + if ($missingColumns.Count -gt 0) { + Write-Error "CSV validation failed: The CSV file needs at least AssetId and DeploymentClusterId columns." + Write-Error "Actual columns found in CSV: $($actualColumns -join ', ')" + exit 1 + } + + # Note: Import-Csv automatically excludes the header row from the data array + + # Enumerate the array and validate each object + for ($i = 0; $i -lt $csvData.Count; $i++) { + $row = $csvData[$i] + $csvRowNumber = $i + 2 # +2 because row 1 is header, and arrays are 0-indexed + + # Check if AssetId is null + if ($null -eq $row.AssetId) { + Write-Error "CSV validation failed: AssetId is null at row $csvRowNumber (index $i)" + exit 1 + } + + # Check if DeploymentClusterId is null + if ($null -eq $row.DeploymentClusterId) { + Write-Error "CSV validation failed: DeploymentClusterId is null at row $csvRowNumber (index $i)" + exit 1 + } + } + + # Return validated CSV data + return $csvData +} + +switch ($PSCmdlet.ParameterSetName) { + "ByAssetId" { + "" + } + "ByCsvPath" { + $csvData = Get-CsvData + } + "ListDeploymentClusters" { + "" + } + "ExportCsvTemplate" { + Export-CsvTemplate + } +} \ No newline at end of file From be8dd4c5fd88ba77324756c8a86997f5202d8313 Mon Sep 17 00:00:00 2001 From: Thomas Obarowski Date: Sat, 20 Dec 2025 11:39:08 -0500 Subject: [PATCH 2/6] (feat) Added function to create script-wide headers / URL --- .../Pin Assets To Clusters/.gitignore | 1 + .../Pin-AssetsToClusters.ps1 | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore b/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore index b0972c0..afca281 100644 --- a/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore @@ -1,2 +1,3 @@ *.csv .env +.env.ps1 diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 b/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 index fbf0073..adcc2c7 100644 --- a/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 @@ -1,4 +1,3 @@ -#requires -Modules ZeroNetworks #requires -Version 7.0 [CmdletBinding(DefaultParameterSetName = "ByAssetId")] @@ -8,6 +7,11 @@ param( [Parameter(ParameterSetName = "ListDeploymentClusters", Mandatory = $true)] [Parameter(ParameterSetName = "ByCsvPath", Mandatory = $true)] [string]$ApiKey, + + [Parameter(ParameterSetName = "ByAssetId", Mandatory = $false)] + [Parameter(ParameterSetName = "ListDeploymentClusters", Mandatory = $false)] + [Parameter(ParameterSetName = "ByCsvPath", Mandatory = $false)] + [string]$PortalUrl = "https://portal.zeronetworks.com", # ParameterSet 1: Pin by Asset ID and Deployment Cluster ID [Parameter(ParameterSetName = "ByAssetId", Mandatory = $true)] @@ -112,17 +116,28 @@ function Get-CsvData { return $csvData } +function Initialize-ApiContext { + $script:Headers = @{ + Accept = "application/json" + Authorization = $ApiKey + } + $script:ApiBaseUrl = "$PortalUrl/api/v1" +} + switch ($PSCmdlet.ParameterSetName) { "ByAssetId" { + Initialize-ApiContext "" } "ByCsvPath" { + Initialize-ApiContext $csvData = Get-CsvData } "ListDeploymentClusters" { + Initialize-ApiContext "" } "ExportCsvTemplate" { Export-CsvTemplate } -} \ No newline at end of file +} From e98695df46d0017767d8fa0ae136b34d868ed08b Mon Sep 17 00:00:00 2001 From: Thomas Obarowski Date: Sat, 20 Dec 2025 13:45:05 -0500 Subject: [PATCH 3/6] (feat): Added listing deployment clusters functionality --- .../Pin-AssetsToClusters.ps1 | 109 +++++++++++++++++- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 b/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 index adcc2c7..f5dabaa 100644 --- a/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 @@ -38,7 +38,6 @@ param( [switch]$ExportCsvTemplate ) - function Export-CsvTemplate { $template = [PSCustomObject]@{ AssetName = $null @@ -46,9 +45,9 @@ function Export-CsvTemplate { DeploymentClusterId = $null } $template | Export-Csv -Path ".\pin-assets-to-clusters-template.csv" -NoTypeInformation - Write-Output "CSV Template exported to .\pin-assets-to-clusters-template.csv" - Write-Output "Please fill in AT LEAST the AssetId and DeploymentClusterId columsn, and then run the script again with the -CsvPath parameter to pin the assets to the clusters." - Write-Output "Example: .\Pin-AssetsToClusters.ps1 -CsvPath '.\pin-assets-to-clusters.csv' -ApiKey 'your-api-key'" + Write-Host "CSV Template exported to .\pin-assets-to-clusters-template.csv" + Write-Host "Please fill in AT LEAST the AssetId and DeploymentClusterId columsn, and then run the script again with the -CsvPath parameter to pin the assets to the clusters." + Write-Host "Example: .\Pin-AssetsToClusters.ps1 -CsvPath '.\pin-assets-to-clusters.csv' -ApiKey 'your-api-key'" Exit 0 } @@ -116,6 +115,55 @@ function Get-CsvData { return $csvData } +function Get-DeploymentClusters { + Write-Host "Getting deployment clusters!" + $response = Invoke-ApiRequest -Method "GET" -ApiEndpoint "environments/cluster" + if (-not $response.items) { + Write-Error "Deployment clusters response is malformed and does not contain 'items' property" + exit 1 + } + if ($response.items.Count -eq 0) { + Write-Error "No deployment clusters found!" + exit 1 + } + if ($response.items -isnot [System.Array]) { + return @($response.items) + } + else { + return $response.items + } +} + +function Write-DeploymentClusters { + param( + [Parameter(Mandatory = $true)] + [object]$DeploymentClusters + ) + foreach ($cluster in $DeploymentClusters){ + Write-Host $("="*($Host.UI.RawUI.WindowSize.Width)) + Write-Host "Deployment cluster: $($cluster.name)" + Write-Host "Cluster ID: $($cluster.id)" + Write-Host "Number of assets in cluster: $($cluster.numOfAssets)" + Write-Host "HA Strategy: $(if ($cluster.strategy -eq 2) { "Active/Active" } else { "Active/Passive" })" + Write-Host "Segment server deployments assigned to this cluster:" + if ($cluster.assignedDeployments.Count -eq 0) { + Write-Host "$("`t"*1)No segment server deploments are assigned to this cluster!" + } + else { + foreach ($deployment in $cluster.assignedDeployments){ + Write-Host "--------------------------------" + Write-Host "$("`t"*1)Name: $($deployment.name)" + Write-Host "$("`t"*1)Deployment ID: $($deployment.id)" + Write-Host "$("`t"*1)Deployment IP Address: $($deployment.internalIpAddress)" + Write-Host "$("`t"*1)Is Preferred Deployment: $(if ($deployment.id -eq $cluster.preferredDeployment.id) { "Yes" } else { "No" })" + Write-Host "--------------------------------" + } + } + Write-Host $("="*($Host.UI.RawUI.WindowSize.Width)) + } +} + + function Initialize-ApiContext { $script:Headers = @{ Accept = "application/json" @@ -124,6 +172,57 @@ function Initialize-ApiContext { $script:ApiBaseUrl = "$PortalUrl/api/v1" } +function Invoke-ApiRequest { + param( + [Parameter(Mandatory = $true)] + [ValidateSet('GET', 'POST', 'PUT', 'PATCH', 'DELETE')] + [string]$Method, + + [Parameter(Mandatory = $true)] + [string]$ApiEndpoint, + + [Parameter(Mandatory = $false)] + [object]$Body = $null + ) + + try { + Write-Host "Sending $Method request to $script:ApiBaseUrl/$ApiEndpoint" + $requestParams = @{ + Method = $Method + Uri = "$script:ApiBaseUrl/$ApiEndpoint" + Headers = $script:Headers + } + + if ($null -ne $Body) { + $requestParams['Body'] = if ($Body -is [string]) { + $Body + } + else { + $Body | ConvertTo-Json -Depth 10 + } + $requestParams['ContentType'] = "application/json" + } + + $response = Invoke-RestMethod @requestParams + return $response + } + catch { + $statusCode = $null + if ($_.Exception.Response) { + $statusCode = [int]$_.Exception.Response.StatusCode + } + + if ($null -ne $statusCode -and $statusCode -ge 400 -and $statusCode -lt 500) { + Write-Error "API request failed: The API key is invalid or you do not have permission to access this resource." + exit 1 + } + else { + Write-Error "API request failed: $_" + exit 1 + } + } +} + switch ($PSCmdlet.ParameterSetName) { "ByAssetId" { Initialize-ApiContext @@ -135,6 +234,8 @@ switch ($PSCmdlet.ParameterSetName) { } "ListDeploymentClusters" { Initialize-ApiContext + $DeploymentClusters = Get-DeploymentClusters + Write-DeploymentClusters -DeploymentClusters $DeploymentClusters "" } "ExportCsvTemplate" { From 4734e7b07e36b74b3a55269a618d491d99186f33 Mon Sep 17 00:00:00 2001 From: Thomas Obarowski Date: Sat, 20 Dec 2025 13:47:07 -0500 Subject: [PATCH 4/6] (maintenance): Updating TODO list --- Segment/Segment/Asset Management/Pin Assets To Clusters/TODO | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Segment/Segment/Asset Management/Pin Assets To Clusters/TODO diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/TODO b/Segment/Segment/Asset Management/Pin Assets To Clusters/TODO new file mode 100644 index 0000000..9347531 --- /dev/null +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/TODO @@ -0,0 +1,3 @@ +# TODO - Add function to validate if get asset details from API - check if asset is pinned to deployment alredy +# TODO - Add function to pin assets to deployment +# TODO - Add function to unpin assets from deployment - validate they are pinned first \ No newline at end of file From 774972937944b0df062da8c3124d81d66d2c4811 Mon Sep 17 00:00:00 2001 From: Thomas Obarowski Date: Sun, 21 Dec 2025 11:29:00 -0500 Subject: [PATCH 5/6] (feat): Finished cluster deployments listing, and asset pin eligibility validation --- .../Pin Assets To Clusters/.gitignore | 1 + .../AssetDetailsFieldMappings.json | 1197 +++++++++++++++++ .../DeploymentClusterFieldMappings.json | 95 ++ .../Pin-AssetsToClusters.ps1 | 369 ++++- .../Pin Assets To Clusters/TODO | 3 +- 5 files changed, 1602 insertions(+), 63 deletions(-) create mode 100644 Segment/Segment/Asset Management/Pin Assets To Clusters/AssetDetailsFieldMappings.json create mode 100644 Segment/Segment/Asset Management/Pin Assets To Clusters/DeploymentClusterFieldMappings.json diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore b/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore index afca281..1a96327 100644 --- a/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore @@ -1,3 +1,4 @@ *.csv .env .env.ps1 + diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/AssetDetailsFieldMappings.json b/Segment/Segment/Asset Management/Pin Assets To Clusters/AssetDetailsFieldMappings.json new file mode 100644 index 0000000..c73186c --- /dev/null +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/AssetDetailsFieldMappings.json @@ -0,0 +1,1197 @@ +{ + "assetType": { + "byId": { + "0": "Undetermined", + "1": "Client", + "2": "Server", + "3": "Virtual cluster", + "4": "IP camera", + "5": "Smart TV", + "6": "Factory controller", + "7": "Medical device", + "8": "Printer", + "9": "Scanner", + "10": "Smart card reader", + "11": "Router", + "12": "Hypervisor", + "13": "PLC", + "14": "HMI", + "15": "Switch", + "16": "Terminal station", + "17": "RTU", + "18": "Wireless access point", + "19": "Historian", + "20": "Game console", + "21": "Fire alarm", + "22": "UPS", + "23": "Storage appliance", + "24": "Virtualization appliance", + "25": "Firewall appliance", + "26": "Security scanner", + "27": "Security controller", + "28": "Door lock", + "29": "Biometric scanner", + "30": "HVAC", + "31": "Room scheduler", + "32": "Load Balancer Appliance", + "33": "WAN Concentrator", + "34": "IPAM Appliance", + "35": "Temperature sensor gateways", + "36": "Power meters", + "37": "Conveyor systems", + "38": "Building automation devices", + "39": "Vision controllers", + "40": "Manufacturing execution systems", + "41": "BACnet broadcast management devices (BBMD)", + "42": "BACnet routers & BBMD", + "43": "Clocks", + "44": "RFID", + "45": "Scales", + "46": "Mobile printers", + "47": "IED", + "48": "CNC mills", + "49": "Room monitors", + "50": "Smart speakers", + "51": "Vending machines", + "52": "Autonomous vehicles", + "53": "Fleet management systems", + "54": "BACnet routers", + "55": "Motor controllers", + "56": "OT gateways", + "57": "Controllers", + "58": "Building management systems", + "59": "VoIP phones", + "60": "Vision sensors", + "61": "Remote I/O", + "62": "VoIP servers", + "63": "Flow meters", + "64": "Building automation controllers", + "65": "Security cameras", + "66": "Digital signs", + "67": "Remote access gateways", + "68": "Temperature sensors", + "69": "RTLS devices", + "70": "Serial-to-Ethernet devices", + "71": "Intercoms", + "72": "Engineering stations", + "73": "Data logger gateways", + "74": "Robot", + "75": "Tablet", + "76": "SCADA Server", + "77": "Industrial Printer", + "78": "Vision Camera", + "79": "Time Clock", + "80": "Video Conference", + "81": "Electrical Drive", + "82": "Access Control", + "83": "Printer 3D", + "84": "Access Control Gateway", + "85": "ATM", + "86": "Audio Decoder", + "87": "AV System", + "88": "BACnet Router BBMD", + "89": "Badge Reader", + "90": "Barcode Scanner", + "91": "Building Automation Controller Gateway", + "92": "Building Management Sensor", + "93": "Building Management Sensor Gateway", + "94": "Building Management System Gateway", + "95": "Car", + "96": "Circuit Monitor", + "97": "Clinical Mobile Device", + "98": "Clinical Mobile Device Gateway", + "99": "Clock Gateway", + "100": "CNC System", + "101": "Conference Room", + "102": "Data Logger", + "103": "Digital Sign Workstation", + "104": "Disk Publisher", + "105": "Dispatcher", + "106": "Embedded PC", + "107": "Encoder", + "108": "EV Charging", + "109": "Fitness Device", + "110": "Generic Mobile Device", + "111": "GPS Navigator", + "112": "Industrial Barcode Scanner", + "113": "Industrial Metal Detector", + "114": "Industrial Network Equipment", + "115": "Industrial Thin Client", + "116": "Industrial Thin Client Gateway", + "117": "Industrial Wireless", + "118": "Industrial Workstation", + "119": "Inspection System", + "120": "Interactive Voice Response", + "121": "Intercom Gateway", + "122": "IoT Controller", + "123": "Lock Box", + "124": "Machinery Health Analyzer", + "125": "Mailing System", + "126": "Media Gateway", + "127": "Media Player", + "128": "Media Streamer", + "129": "Microscope", + "130": "Mobile Computer", + "131": "Motor Starter", + "132": "Musical Instrument", + "133": "OPC Server", + "134": "Order Fulfillment System", + "135": "OT Device", + "136": "OT Server", + "137": "PA System", + "138": "Payment Kiosk", + "139": "Physical Security Platform", + "140": "Point of Sale", + "141": "Point of Sale Gateway", + "142": "Power Distribution Unit", + "143": "Power Distribution Unit Gateway", + "144": "Power Supply", + "145": "Print Server", + "146": "Projector", + "147": "Radio", + "148": "Radio Repeater", + "149": "Recording Server", + "150": "Reporting Server", + "151": "Room Automation Station", + "152": "Room Display", + "153": "RTLS Gateway", + "154": "Safety System", + "155": "SCADA Client", + "156": "Screen Share", + "157": "Security Xray Scanner", + "158": "Sensor", + "159": "Serial-to-Ethernet Gateway", + "160": "Service Trolley", + "161": "Smart Board", + "162": "Smart Home", + "163": "Smart Light", + "164": "Smart Light Gateway", + "165": "Smart Lock Controller", + "166": "Smartphone", + "167": "Smartwatch", + "168": "Solar Energy", + "169": "Time Clock Gateway", + "170": "Video Conference Gateway", + "171": "Video Decoder", + "172": "Video Encoder", + "173": "Video Surveillance Device", + "174": "VoIP Adapter", + "175": "VoIP Phone Gateway", + "176": "Water System", + "177": "Wireless Phone", + "178": "Wireless Phone Gateway", + "1001": "Other OT" + }, + "byName": { + "Undetermined": 0, + "Client": 1, + "Server": 2, + "Virtual cluster": 3, + "IP camera": 4, + "Smart TV": 5, + "Factory controller": 6, + "Medical device": 7, + "Printer": 8, + "Scanner": 9, + "Smart card reader": 10, + "Router": 11, + "Hypervisor": 12, + "PLC": 13, + "HMI": 14, + "Switch": 15, + "Terminal station": 16, + "RTU": 17, + "Wireless access point": 18, + "Historian": 19, + "Game console": 20, + "Fire alarm": 21, + "UPS": 22, + "Storage appliance": 23, + "Virtualization appliance": 24, + "Firewall appliance": 25, + "Security scanner": 26, + "Security controller": 27, + "Door lock": 28, + "Biometric scanner": 29, + "HVAC": 30, + "Room scheduler": 31, + "Load Balancer Appliance": 32, + "WAN Concentrator": 33, + "IPAM Appliance": 34, + "Temperature sensor gateways": 35, + "Power meters": 36, + "Conveyor systems": 37, + "Building automation devices": 38, + "Vision controllers": 39, + "Manufacturing execution systems": 40, + "BACnet broadcast management devices (BBMD)": 41, + "BACnet routers & BBMD": 42, + "Clocks": 43, + "RFID": 44, + "Scales": 45, + "Mobile printers": 46, + "IED": 47, + "CNC mills": 48, + "Room monitors": 49, + "Smart speakers": 50, + "Vending machines": 51, + "Autonomous vehicles": 52, + "Fleet management systems": 53, + "BACnet routers": 54, + "Motor controllers": 55, + "OT gateways": 56, + "Controllers": 57, + "Building management systems": 58, + "VoIP phones": 59, + "Vision sensors": 60, + "Remote I/O": 61, + "VoIP servers": 62, + "Flow meters": 63, + "Building automation controllers": 64, + "Security cameras": 65, + "Digital signs": 66, + "Remote access gateways": 67, + "Temperature sensors": 68, + "RTLS devices": 69, + "Serial-to-Ethernet devices": 70, + "Intercoms": 71, + "Engineering stations": 72, + "Data logger gateways": 73, + "Robot": 74, + "Tablet": 75, + "SCADA Server": 76, + "Industrial Printer": 77, + "Vision Camera": 78, + "Time Clock": 79, + "Video Conference": 80, + "Electrical Drive": 81, + "Access Control": 82, + "Printer 3D": 83, + "Access Control Gateway": 84, + "ATM": 85, + "Audio Decoder": 86, + "AV System": 87, + "BACnet Router BBMD": 88, + "Badge Reader": 89, + "Barcode Scanner": 90, + "Building Automation Controller Gateway": 91, + "Building Management Sensor": 92, + "Building Management Sensor Gateway": 93, + "Building Management System Gateway": 94, + "Car": 95, + "Circuit Monitor": 96, + "Clinical Mobile Device": 97, + "Clinical Mobile Device Gateway": 98, + "Clock Gateway": 99, + "CNC System": 100, + "Conference Room": 101, + "Data Logger": 102, + "Digital Sign Workstation": 103, + "Disk Publisher": 104, + "Dispatcher": 105, + "Embedded PC": 106, + "Encoder": 107, + "EV Charging": 108, + "Fitness Device": 109, + "Generic Mobile Device": 110, + "GPS Navigator": 111, + "Industrial Barcode Scanner": 112, + "Industrial Metal Detector": 113, + "Industrial Network Equipment": 114, + "Industrial Thin Client": 115, + "Industrial Thin Client Gateway": 116, + "Industrial Wireless": 117, + "Industrial Workstation": 118, + "Inspection System": 119, + "Interactive Voice Response": 120, + "Intercom Gateway": 121, + "IoT Controller": 122, + "Lock Box": 123, + "Machinery Health Analyzer": 124, + "Mailing System": 125, + "Media Gateway": 126, + "Media Player": 127, + "Media Streamer": 128, + "Microscope": 129, + "Mobile Computer": 130, + "Motor Starter": 131, + "Musical Instrument": 132, + "OPC Server": 133, + "Order Fulfillment System": 134, + "OT Device": 135, + "OT Server": 136, + "PA System": 137, + "Payment Kiosk": 138, + "Physical Security Platform": 139, + "Point of Sale": 140, + "Point of Sale Gateway": 141, + "Power Distribution Unit": 142, + "Power Distribution Unit Gateway": 143, + "Power Supply": 144, + "Print Server": 145, + "Projector": 146, + "Radio": 147, + "Radio Repeater": 148, + "Recording Server": 149, + "Reporting Server": 150, + "Room Automation Station": 151, + "Room Display": 152, + "RTLS Gateway": 153, + "Safety System": 154, + "SCADA Client": 155, + "Screen Share": 156, + "Security Xray Scanner": 157, + "Sensor": 158, + "Serial-to-Ethernet Gateway": 159, + "Service Trolley": 160, + "Smart Board": 161, + "Smart Home": 162, + "Smart Light": 163, + "Smart Light Gateway": 164, + "Smart Lock Controller": 165, + "Smartphone": 166, + "Smartwatch": 167, + "Solar Energy": 168, + "Time Clock Gateway": 169, + "Video Conference Gateway": 170, + "Video Decoder": 171, + "Video Encoder": 172, + "Video Surveillance Device": 173, + "VoIP Adapter": 174, + "VoIP Phone Gateway": 175, + "Water System": 176, + "Wireless Phone": 177, + "Wireless Phone Gateway": 178, + "Other OT": 1001, + "ASSET_TYPE_UNKNOWN": 0, + "CLIENT": 1, + "SERVER": 2, + "CLUSTER": 3, + "CAMERA": 4, + "TV": 5, + "FACTORY_CONTROLLER": 6, + "MEDICAL_DEVICE": 7, + "PRINTER": 8, + "SCANNER": 9, + "SMART_CARD_READER": 10, + "ROUTER": 11, + "HYPERVISOR": 12, + "PLC": 13, + "HMI": 14, + "SWITCH": 15, + "TERMINAL_STATION": 16, + "RTU": 17, + "WIRELESS_ACCESS_POINT": 18, + "HISTORIAN": 19, + "GAME_CONSOLE": 20, + "FIRE_ALARM": 21, + "UPS": 22, + "STORAGE_APPLIANCE": 23, + "VIRTUALIZATION_APPLIANCE": 24, + "FIREWALL_APPLIANCE": 25, + "SECURITY_SCANNER": 26, + "SECURITY_CONTROLLER": 27, + "DOOR_LOCK": 28, + "BIOMETRIC_SCANNER": 29, + "HVAC": 30, + "ROOM_SCHEDULER": 31, + "LOAD_BALANCER_APPLIANCE": 32, + "WAN_CONCENTRATOR": 33, + "IPAM_APPLIANCE": 34, + "TEMPERATURE_SENSOR_GATEWAY": 35, + "POWER_METER": 36, + "CONVEYOR_SYSTEM": 37, + "BUILDING_AUTOMATION_DEVICE": 38, + "VISION_CONTROLLER": 39, + "MANUFACTURING_EXECUTION_SYSTEM": 40, + "BACNET_BROADCAST_MANAGEMENT_DEVICE_BBMD": 41, + "BACNET_ROUTER_AND_BBMD": 42, + "CLOCK": 43, + "RFID": 44, + "SCALE": 45, + "MOBILE_PRINTER": 46, + "IED": 47, + "CNC_MILL": 48, + "ROOM_MONITOR": 49, + "SMART_SPEAKER": 50, + "VENDING_MACHINE": 51, + "AUTONOMOUS_VEHICLE": 52, + "FLEET_MANAGEMENT_SYSTEM": 53, + "BACNET_ROUTER": 54, + "MOTOR_CONTROLLER": 55, + "OT_GATEWAY": 56, + "CONTROLLER": 57, + "BUILDING_MANAGEMENT_SYSTEM": 58, + "VOIP_PHONE": 59, + "VISION_SENSOR": 60, + "REMOTE_IO": 61, + "VOIP_SERVER": 62, + "FLOW_METER": 63, + "BUILDING_AUTOMATION_CONTROLLER": 64, + "SECURITY_CAMERA": 65, + "DIGITAL_SIGN": 66, + "REMOTE_ACCESS_GATEWAY": 67, + "TEMPERATURE_SENSOR": 68, + "RTLS": 69, + "SERIAL_TO_ETHERNET": 70, + "INTERCOM": 71, + "ENGINEERING_STATION": 72, + "DATA_LOGGER_GATEWAY": 73, + "ROBOT": 74, + "TABLET": 75, + "SCADA_SERVER": 76, + "INDUSTRIAL_PRINTER": 77, + "VISION_CAMERA": 78, + "TIME_CLOCK": 79, + "VIDEO_CONFERENCE": 80, + "ELECTRICAL_DRIVE": 81, + "ACCESS_CONTROL": 82, + "PRINTER_3D": 83, + "ACCESS_CONTROL_GATEWAY": 84, + "ATM": 85, + "AUDIO_DECODER": 86, + "AV_SYSTEM": 87, + "BACNET_ROUTER_BBMD": 88, + "BADGE_READER": 89, + "BARCODE_SCANNER": 90, + "BUILDING_AUTOMATION_CONTROLLER_GATEWAY": 91, + "BUILDING_MANAGEMENT_SENSOR": 92, + "BUILDING_MANAGEMENT_SENSOR_GATEWAY": 93, + "BUILDING_MANAGEMENT_SYSTEM_GATEWAY": 94, + "CAR": 95, + "CIRCUIT_MONITOR": 96, + "CLINICAL_MOBILE_DEVICE": 97, + "CLINICAL_MOBILE_DEVICE_GATEWAY": 98, + "CLOCK_GATEWAY": 99, + "CNC_SYSTEM": 100, + "CONFERENCE_ROOM": 101, + "DATA_LOGGER": 102, + "DIGITAL_SIGN_WORKSTATION": 103, + "DISK_PUBLISHER": 104, + "DISPATCHER": 105, + "EMBEDDED_PC": 106, + "ENCODER": 107, + "EV_CHARGING": 108, + "FITNESS_DEVICE": 109, + "GENERIC_MOBILE_DEVICE": 110, + "GPS_NAVIGATOR": 111, + "INDUSTRIAL_BARCODE_SCANNER": 112, + "INDUSTRIAL_METAL_DETECTOR": 113, + "INDUSTRIAL_NETWORK_EQUIPMENT": 114, + "INDUSTRIAL_THIN_CLIENT": 115, + "INDUSTRIAL_THIN_CLIENT_GATEWAY": 116, + "INDUSTRIAL_WIRELESS": 117, + "INDUSTRIAL_WORKSTATION": 118, + "INSPECTION_SYSTEM": 119, + "INTERACTIVE_VOICE_RESPONSE": 120, + "INTERCOM_GATEWAY": 121, + "IOT_CONTROLLER": 122, + "LOCK_BOX": 123, + "MACHINERY_HEALTH_ANALYZER": 124, + "MAILING_SYSTEM": 125, + "MEDIA_GATEWAY": 126, + "MEDIA_PLAYER": 127, + "MEDIA_STREAMER": 128, + "MICROSCOPE": 129, + "MOBILE_COMPUTER": 130, + "MOTOR_STARTER": 131, + "MUSICAL_INSTRUMENT": 132, + "OPC_SERVER": 133, + "ORDER_FULFILLMENT_SYSTEM": 134, + "OT_DEVICE": 135, + "OT_SERVER": 136, + "PA_SYSTEM": 137, + "PAYMENT_KIOSK": 138, + "PHYSICAL_SECURITY_PLATFORM": 139, + "POINT_OF_SALE": 140, + "POINT_OF_SALE_GATEWAY": 141, + "POWER_DISTRIBUTION_UNIT": 142, + "POWER_DISTRIBUTION_UNIT_GATEWAY": 143, + "POWER_SUPPLY": 144, + "PRINT_SERVER": 145, + "PROJECTOR": 146, + "RADIO": 147, + "RADIO_REPEATER": 148, + "RECORDING_SERVER": 149, + "REPORTING_SERVER": 150, + "ROOM_AUTOMATION_STATION": 151, + "ROOM_DISPLAY": 152, + "RTLS_GATEWAY": 153, + "SAFETY_SYSTEM": 154, + "SCADA_CLIENT": 155, + "SCREEN_SHARE": 156, + "SECURITY_XRAY_SCANNER": 157, + "SENSOR": 158, + "SERIAL_TO_ETHERNET_GATEWAY": 159, + "SERVICE_TROLLEY": 160, + "SMART_BOARD": 161, + "SMART_HOME": 162, + "SMART_LIGHT": 163, + "SMART_LIGHT_GATEWAY": 164, + "SMART_LOCK_CONTROLLER": 165, + "SMARTPHONE": 166, + "SMARTWATCH": 167, + "SOLAR_ENERGY": 168, + "TIME_CLOCK_GATEWAY": 169, + "VIDEO_CONFERENCE_GATEWAY": 170, + "VIDEO_DECODER": 171, + "VIDEO_ENCODER": 172, + "VIDEO_SURVEILLANCE_DEVICE": 173, + "VOIP_ADAPTER": 174, + "VOIP_PHONE_GATEWAY": 175, + "WATER_SYSTEM": 176, + "WIRELESS_PHONE": 177, + "WIRELESS_PHONE_GATEWAY": 178, + "OTHER_OT": 1001 + } + }, + "protectionState": { + "byId": { + "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", + "1": "Not segmented", + "2": "Unsegmenting", + "3": "Segmented", + "4": "Segmenting", + "5": "In learning", + "6": "Not segmented (manual)", + "7": "Unsegmenting (manual)", + "8": "Segmented (policy)", + "9": "Segmenting (policy)", + "10": "In learning (policy)", + "11": "Learning done", + "12": "Learning (policy) done", + "13": "Enforcing block rules", + "14": "Enforcing block rules (policy)", + "15": "In learning with blocks", + "16": "In learning with blocks (policy)", + "17": "Learning with blocks done", + "18": "Learning with blocks (policy) done" + }, + "byName": { + "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, + "Not segmented": 1, + "Unsegmenting": 2, + "Segmented": 3, + "Segmenting": 4, + "In learning": 5, + "Not segmented (manual)": 6, + "Unsegmenting (manual)": 7, + "Segmented (policy)": 8, + "Segmenting (policy)": 9, + "In learning (policy)": 10, + "Learning done": 11, + "Learning (policy) done": 12, + "Enforcing block rules": 13, + "Enforcing block rules (policy)": 14, + "In learning with blocks": 15, + "In learning with blocks (policy)": 16, + "Learning with blocks done": 17, + "Learning with blocks (policy) done": 18, + "ASSET_PROTECTION_STATE_UNPROTECTED": 1, + "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, + "ASSET_PROTECTION_STATE_PROTECTED": 3, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, + "ASSET_PROTECTION_STATE_QUEUED": 5, + "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, + "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, + "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, + "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 + } + }, + "identityProtectionState": { + "byId": { + "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", + "1": "Not segmented", + "2": "Unsegmenting", + "3": "Segmented", + "4": "Segmenting", + "5": "In learning", + "6": "Not segmented (manual)", + "7": "Unsegmenting (manual)", + "8": "Segmented (policy)", + "9": "Segmenting (policy)", + "10": "In learning (policy)", + "11": "Learning done", + "12": "Learning (policy) done", + "13": "Enforcing block rules", + "14": "Enforcing block rules (policy)", + "15": "In learning with blocks", + "16": "In learning with blocks (policy)", + "17": "Learning with blocks done", + "18": "Learning with blocks (policy) done" + }, + "byName": { + "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, + "Not segmented": 1, + "Unsegmenting": 2, + "Segmented": 3, + "Segmenting": 4, + "In learning": 5, + "Not segmented (manual)": 6, + "Unsegmenting (manual)": 7, + "Segmented (policy)": 8, + "Segmenting (policy)": 9, + "In learning (policy)": 10, + "Learning done": 11, + "Learning (policy) done": 12, + "Enforcing block rules": 13, + "Enforcing block rules (policy)": 14, + "In learning with blocks": 15, + "In learning with blocks (policy)": 16, + "Learning with blocks done": 17, + "Learning with blocks (policy) done": 18, + "ASSET_PROTECTION_STATE_UNPROTECTED": 1, + "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, + "ASSET_PROTECTION_STATE_PROTECTED": 3, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, + "ASSET_PROTECTION_STATE_QUEUED": 5, + "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, + "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, + "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, + "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 + } + }, + "rpcProtectionState": { + "byId": { + "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", + "1": "Not segmented", + "2": "Unsegmenting", + "3": "Segmented", + "4": "Segmenting", + "5": "In learning", + "6": "Not segmented (manual)", + "7": "Unsegmenting (manual)", + "8": "Segmented (policy)", + "9": "Segmenting (policy)", + "10": "In learning (policy)", + "11": "Learning done", + "12": "Learning (policy) done", + "13": "Enforcing block rules", + "14": "Enforcing block rules (policy)", + "15": "In learning with blocks", + "16": "In learning with blocks (policy)", + "17": "Learning with blocks done", + "18": "Learning with blocks (policy) done" + }, + "byName": { + "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, + "Not segmented": 1, + "Unsegmenting": 2, + "Segmented": 3, + "Segmenting": 4, + "In learning": 5, + "Not segmented (manual)": 6, + "Unsegmenting (manual)": 7, + "Segmented (policy)": 8, + "Segmenting (policy)": 9, + "In learning (policy)": 10, + "Learning done": 11, + "Learning (policy) done": 12, + "Enforcing block rules": 13, + "Enforcing block rules (policy)": 14, + "In learning with blocks": 15, + "In learning with blocks (policy)": 16, + "Learning with blocks done": 17, + "Learning with blocks (policy) done": 18, + "ASSET_PROTECTION_STATE_UNPROTECTED": 1, + "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, + "ASSET_PROTECTION_STATE_PROTECTED": 3, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, + "ASSET_PROTECTION_STATE_QUEUED": 5, + "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, + "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, + "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, + "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 + } + }, + "assetStatus": { + "byId": { + "0": "ASSET_STATUS_UNSPECIFIED", + "1": "Not monitored", + "2": "Segment server", + "4": "Can't be monitored (unsupported OS)", + "5": "Can't be monitored (unmonitorable)", + "6": "Can't be monitored (unmonitorable)", + "7": "Cloud connector", + "8": "Not monitored (ansible unreachable)", + "9": "Not monitored (cloud connector uninstalled)", + "10": "Not monitored (cloud connector required)", + "12": "Can't be monitored (inactive entity)", + "13": "Segment server", + "14": "Segment connector" + }, + "byName": { + "ASSET_STATUS_UNSPECIFIED": 0, + "Not monitored": 1, + "Segment server": 2, + "Can't be monitored (unsupported OS)": 4, + "Can't be monitored (unmonitorable)": 5, + "Can't be monitored (unmonitorable)": 6, + "Cloud connector": 7, + "Not monitored (ansible unreachable)": 8, + "Not monitored (cloud connector uninstalled)": 9, + "Not monitored (cloud connector required)": 10, + "Can't be monitored (inactive entity)": 12, + "Segment server": 13, + "Segment connector": 14, + "UNDISCOVERED": 1, + "STALKED": 2, + "OS_UNSUPPORTED": 4, + "TYPE_UNSUPPORTED": 5, + "IGNORED": 6, + "AGENT": 7, + "UNREACHABLE": 8, + "AGENT_UNINSTALLED": 9, + "PENDING_AGENT": 10, + "DELETED": 12, + "STALKED_EXTERNALLY": 13, + "LIGHTWEIGHT_AGENT": 14 + } + }, + "osType": { + "byId": { + "0": "OS_TYPE_UNSPECIFIED", + "1": "Unmanageable OS", + "2": "Windows", + "3": "Linux", + "4": "macOS" + }, + "byName": { + "OS_TYPE_UNSPECIFIED": 0, + "Unmanageable OS": 1, + "Windows": 2, + "Linux": 3, + "macOS": 4, + "UNMANAGEABLE": 1, + "WINDOWS": 2, + "LINUX": 3, + "MAC": 4 + } + }, + "source": { + "byId": { + "0": "ENTITY_SOURCE_UNSPECIFIED", + "1": "Access portal", + "2": "Access portal", + "3": "Active directory", + "4": "Custom", + "5": "System", + "6": "Ansible", + "7": "Manual OT/IoT", + "8": "Workgroup", + "9": "Entra ID (Azure AD)", + "10": "Azure", + "11": "AWS", + "12": "GCP", + "13": "Tag", + "14": "Jamf", + "15": "Manual Linux", + "16": "IBM cloud", + "17": "Oracle cloud", + "18": "VMware cloud", + "19": "Alibaba cloud", + "20": "Lumen cloud", + "21": "OVH cloud", + "22": "Connect", + "23": "AI", + "24": "ServiceNow", + "25": "Google workspace", + "26": "OU", + "27": "Environment", + "28": "Conditional", + "29": "Claroty", + "30": "Manual Mac" + }, + "byName": { + "ENTITY_SOURCE_UNSPECIFIED": 0, + "Access portal": 1, + "Active directory": 3, + "Custom": 4, + "System": 5, + "Ansible": 6, + "Manual OT/IoT": 7, + "Workgroup": 8, + "Entra ID (Azure AD)": 9, + "Azure": 10, + "AWS": 11, + "GCP": 12, + "Tag": 13, + "Jamf": 14, + "Manual Linux": 15, + "IBM cloud": 16, + "Oracle cloud": 17, + "VMware cloud": 18, + "Alibaba cloud": 19, + "Lumen cloud": 20, + "OVH cloud": 21, + "Connect": 22, + "AI": 23, + "ServiceNow": 24, + "Google workspace": 25, + "OU": 26, + "Environment": 27, + "Conditional": 28, + "Claroty": 29, + "Manual Mac": 30, + "ENTITY_SOURCE_PORTAL": 1, + "ENTITY_SOURCE_SSP": 2, + "ENTITY_SOURCE_AD": 3, + "ENTITY_SOURCE_CUSTOM": 4, + "ENTITY_SOURCE_SYSTEM": 5, + "ENTITY_SOURCE_ANSIBLE": 6, + "ENTITY_SOURCE_MANUAL_OT": 7, + "ENTITY_SOURCE_WORKGROUP": 8, + "ENTITY_SOURCE_AZURE_AD": 9, + "ENTITY_SOURCE_AZURE": 10, + "ENTITY_SOURCE_AWS": 11, + "ENTITY_SOURCE_GCP": 12, + "ENTITY_SOURCE_TAG": 13, + "ENTITY_SOURCE_JAMF": 14, + "ENTITY_SOURCE_LINUX": 15, + "ENTITY_SOURCE_IBM": 16, + "ENTITY_SOURCE_ORACLE": 17, + "ENTITY_SOURCE_VMWARE": 18, + "ENTITY_SOURCE_ALIBABA": 19, + "ENTITY_SOURCE_LUMEN": 20, + "ENTITY_SOURCE_OVH": 21, + "ENTITY_SOURCE_VPN": 22, + "ENTITY_SOURCE_AI": 23, + "ENTITY_SOURCE_SNOW": 24, + "ENTITY_SOURCE_GOOGLE_WORKSPACE": 25, + "ENTITY_SOURCE_OU": 26, + "ENTITY_SOURCE_ENVIRONMENT": 27, + "ENTITY_SOURCE_CONDITIONAL": 28, + "ENTITY_SOURCE_CLAROTY_OT": 29, + "ENTITY_SOURCE_MANUAL_MAC": 30 + } + }, + "healthState.healthStatus": { + "byId": { + "0": "Unknown", + "1": "Healthy", + "2": "Error", + "3": "Warning", + "4": "N/A", + "5": "Retrying", + "6": "Blocker" + }, + "byName": { + "Unknown": 0, + "Healthy": 1, + "Error": 2, + "Warning": 3, + "N/A": 4, + "Retrying": 5, + "Blocker": 6, + "ASSET_HEALTH_STATUS_UNSPECIFIED": 0, + "HEALTHY": 1, + "ERROR": 2, + "WARNING": 3, + "UNSUPPORTED": 4, + "RETRY": 5, + "BLOCKER": 6 + } + }, + "deploymentsClusterSource": { + "byId": { + "0": "ASSIGNED_ASSET_DEPLOYMENTS_CLUSTER_SOURCE_UNKNOWN", + "1": "SYSTEM", + "2": "USER", + "3": "DOMAIN", + "4": "SUBNET", + "5": "NONE", + "6": "NOT_APPLICABLE" + }, + "byName": { + "ASSIGNED_ASSET_DEPLOYMENTS_CLUSTER_SOURCE_UNKNOWN": 0, + "SYSTEM": 1, + "USER": 2, + "DOMAIN": 3, + "SUBNET": 4, + "NONE": 5, + "NOT_APPLICABLE": 6 + } + }, + "inactiveReason": { + "byId": { + "0": "INACTIVE_REASON_UNSPECIFIED", + "1": "Duplicated in asset repository", + "2": "Manually set as inactive", + "3": "", + "4": "Disabled in asset repository", + "5": "Not active in asset repository", + "6": "Deleted in asset repository" + }, + "byName": { + "INACTIVE_REASON_UNSPECIFIED": 0, + "Duplicated in asset repository": 1, + "Manually set as inactive": 2, + "": 3, + "Disabled in asset repository": 4, + "Not active in asset repository": 5, + "Deleted in asset repository": 6, + "REPO_DUPLICATED": 1, + "MANUAL": 2, + "NONE": 3, + "REPO_DISABLED": 4, + "REPO_INACTIVITY": 5, + "REPO_DELETED": 6 + } + }, + "outboundRestriction": { + "byId": { + "0": "OUTBOUND_RESTRICTION_UNSPECIFIED", + "1": "Outbound is not restricted", + "2": "Outbound internal is restricted", + "3": "Outbound external is restricted", + "4": "Outbound internal and external is restricted", + "5": "NO_APPLICABLE" + }, + "byName": { + "OUTBOUND_RESTRICTION_UNSPECIFIED": 0, + "Outbound is not restricted": 1, + "Outbound internal is restricted": 2, + "Outbound external is restricted": 3, + "Outbound internal and external is restricted": 4, + "NO_APPLICABLE": 5, + "NO_RESTRICTION": 1, + "RESTRICTED_INTERNAL": 2, + "RESTRICTED_EXTERNAL": 3, + "RESTRICTED_INTERNAL_EXTERNAL": 4 + } + }, + "enforcementMethod": { + "byId": { + "0": "N/A", + "1": "IP Tables", + "2": "NF Tables", + "3": "Windows Firewall", + "4": "WFP" + }, + "byName": { + "N/A": 0, + "IP Tables": 1, + "NF Tables": 2, + "Windows Firewall": 3, + "WFP": 4, + "ASSET_FIREWALL_TYPE_UNSPECIFIED": 0, + "LINUX_IPTABLES": 1, + "LINUX_NFTABLES": 2, + "WINDOWS_FIREWALL": 3, + "WINDOWS_WFP": 4 + } + }, + "state.protectionState": { + "byId": { + "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", + "1": "Not segmented", + "2": "Unsegmenting", + "3": "Segmented", + "4": "Segmenting", + "5": "In learning", + "6": "Not segmented (manual)", + "7": "Unsegmenting (manual)", + "8": "Segmented (policy)", + "9": "Segmenting (policy)", + "10": "In learning (policy)", + "11": "Learning done", + "12": "Learning (policy) done", + "13": "Enforcing block rules", + "14": "Enforcing block rules (policy)", + "15": "In learning with blocks", + "16": "In learning with blocks (policy)", + "17": "Learning with blocks done", + "18": "Learning with blocks (policy) done" + }, + "byName": { + "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, + "Not segmented": 1, + "Unsegmenting": 2, + "Segmented": 3, + "Segmenting": 4, + "In learning": 5, + "Not segmented (manual)": 6, + "Unsegmenting (manual)": 7, + "Segmented (policy)": 8, + "Segmenting (policy)": 9, + "In learning (policy)": 10, + "Learning done": 11, + "Learning (policy) done": 12, + "Enforcing block rules": 13, + "Enforcing block rules (policy)": 14, + "In learning with blocks": 15, + "In learning with blocks (policy)": 16, + "Learning with blocks done": 17, + "Learning with blocks (policy) done": 18, + "ASSET_PROTECTION_STATE_UNPROTECTED": 1, + "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, + "ASSET_PROTECTION_STATE_PROTECTED": 3, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, + "ASSET_PROTECTION_STATE_QUEUED": 5, + "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, + "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, + "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, + "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 + } + }, + "state.identityProtectionState": { + "byId": { + "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", + "1": "Not segmented", + "2": "Unsegmenting", + "3": "Segmented", + "4": "Segmenting", + "5": "In learning", + "6": "Not segmented (manual)", + "7": "Unsegmenting (manual)", + "8": "Segmented (policy)", + "9": "Segmenting (policy)", + "10": "In learning (policy)", + "11": "Learning done", + "12": "Learning (policy) done", + "13": "Enforcing block rules", + "14": "Enforcing block rules (policy)", + "15": "In learning with blocks", + "16": "In learning with blocks (policy)", + "17": "Learning with blocks done", + "18": "Learning with blocks (policy) done" + }, + "byName": { + "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, + "Not segmented": 1, + "Unsegmenting": 2, + "Segmented": 3, + "Segmenting": 4, + "In learning": 5, + "Not segmented (manual)": 6, + "Unsegmenting (manual)": 7, + "Segmented (policy)": 8, + "Segmenting (policy)": 9, + "In learning (policy)": 10, + "Learning done": 11, + "Learning (policy) done": 12, + "Enforcing block rules": 13, + "Enforcing block rules (policy)": 14, + "In learning with blocks": 15, + "In learning with blocks (policy)": 16, + "Learning with blocks done": 17, + "Learning with blocks (policy) done": 18, + "ASSET_PROTECTION_STATE_UNPROTECTED": 1, + "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, + "ASSET_PROTECTION_STATE_PROTECTED": 3, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, + "ASSET_PROTECTION_STATE_QUEUED": 5, + "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, + "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, + "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, + "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 + } + }, + "state.rpcProtectionState": { + "byId": { + "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", + "1": "Not segmented", + "2": "Unsegmenting", + "3": "Segmented", + "4": "Segmenting", + "5": "In learning", + "6": "Not segmented (manual)", + "7": "Unsegmenting (manual)", + "8": "Segmented (policy)", + "9": "Segmenting (policy)", + "10": "In learning (policy)", + "11": "Learning done", + "12": "Learning (policy) done", + "13": "Enforcing block rules", + "14": "Enforcing block rules (policy)", + "15": "In learning with blocks", + "16": "In learning with blocks (policy)", + "17": "Learning with blocks done", + "18": "Learning with blocks (policy) done" + }, + "byName": { + "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, + "Not segmented": 1, + "Unsegmenting": 2, + "Segmented": 3, + "Segmenting": 4, + "In learning": 5, + "Not segmented (manual)": 6, + "Unsegmenting (manual)": 7, + "Segmented (policy)": 8, + "Segmenting (policy)": 9, + "In learning (policy)": 10, + "Learning done": 11, + "Learning (policy) done": 12, + "Enforcing block rules": 13, + "Enforcing block rules (policy)": 14, + "In learning with blocks": 15, + "In learning with blocks (policy)": 16, + "Learning with blocks done": 17, + "Learning with blocks (policy) done": 18, + "ASSET_PROTECTION_STATE_UNPROTECTED": 1, + "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, + "ASSET_PROTECTION_STATE_PROTECTED": 3, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, + "ASSET_PROTECTION_STATE_QUEUED": 5, + "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, + "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, + "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, + "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, + "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, + "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, + "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, + "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 + } + } + } \ No newline at end of file diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/DeploymentClusterFieldMappings.json b/Segment/Segment/Asset Management/Pin Assets To Clusters/DeploymentClusterFieldMappings.json new file mode 100644 index 0000000..06f56b0 --- /dev/null +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/DeploymentClusterFieldMappings.json @@ -0,0 +1,95 @@ +{ + "strategy": { + "byId": { + "0": "CLUSTER_STRATEGY_UNSPECIFIED", + "1": "Active / Passive", + "2": "Active / Active" + }, + "byName": { + "CLUSTER_STRATEGY_UNSPECIFIED": 0, + "Active / Passive": 1, + "Active / Active": 2, + "ACTIVE_PASSIVE": 1, + "ACTIVE_ACTIVE": 2 + } + }, + "assignedDeployments.status": { + "byId": { + "0": "DEPLOYMENT_STATUS_UNSPECIFIED", + "1": "Offline", + "2": "Online", + "3": "Network disconnected" + }, + "byName": { + "DEPLOYMENT_STATUS_UNSPECIFIED": 0, + "Offline": 1, + "Online": 2, + "Network disconnected": 3, + "DISCONNECTED": 1, + "ONLINE": 2, + "NETWORK_DISCONNECTED": 3 + } + }, + "assignedDeployments.state": { + "byId": { + "0": "DEPLOYMENT_STATE_UNSPECIFIED", + "1": "Primary", + "2": "Secondary" + }, + "byName": { + "DEPLOYMENT_STATE_UNSPECIFIED": 0, + "Primary": 1, + "Secondary": 2, + "DEPLOYMENT_STATE_PRIMARY": 1, + "DEPLOYMENT_STATE_SECONDARY": 2 + } + }, + "assignedDeployments.servicesInfo.serviceId": { + "byId": { + "0": "SERVICE_ID_UNSPECIFIED", + "1": "ad", + "2": "winrm", + "3": "ansible-manager" + }, + "byName": { + "SERVICE_ID_UNSPECIFIED": 0, + "ad": 1, + "winrm": 2, + "ansible-manager": 3, + "SERVICE_ID_AD": 1, + "SERVICE_ID_WINRM": 2, + "SERVICE_ID_ANSIBLE_MANAGER": 3 + } + }, + "assignedDeployments.servicesInfo.status": { + "byId": { + "0": "DEPLOYMENT_STATUS_UNSPECIFIED", + "1": "Offline", + "2": "Online", + "3": "Network disconnected" + }, + "byName": { + "DEPLOYMENT_STATUS_UNSPECIFIED": 0, + "Offline": 1, + "Online": 2, + "Network disconnected": 3, + "DISCONNECTED": 1, + "ONLINE": 2, + "NETWORK_DISCONNECTED": 3 + } + }, + "assignedDeployments.servicesInfo.state": { + "byId": { + "0": "DEPLOYMENT_STATE_UNSPECIFIED", + "1": "Primary", + "2": "Secondary" + }, + "byName": { + "DEPLOYMENT_STATE_UNSPECIFIED": 0, + "Primary": 1, + "Secondary": 2, + "DEPLOYMENT_STATE_PRIMARY": 1, + "DEPLOYMENT_STATE_SECONDARY": 2 + } + } + } \ No newline at end of file diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 b/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 index f5dabaa..391710b 100644 --- a/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 @@ -15,7 +15,7 @@ param( # ParameterSet 1: Pin by Asset ID and Deployment Cluster ID [Parameter(ParameterSetName = "ByAssetId", Mandatory = $true)] - [string[]]$AssetId, + [string]$AssetId, [Parameter(ParameterSetName = "ByAssetId", Mandatory = $true)] [string]$DeploymentClusterId, @@ -38,6 +38,222 @@ param( [switch]$ExportCsvTemplate ) + +<# +This section of the script contains all of the +asset related functions in the script +#> +function Test-AssetCanBePinned { + param( + [Parameter(Mandatory = $true)] + [object]$AssetDetails + ) + # ORDER OF VALIDATION MATTERS HERE! + # 1st: Check if asset is monitored by a Segment Server, if not, error out. + if ($AssetDetails.assetStatus -ne 2) { + Write-Error "Asset $($AssetDetails.name) ($($AssetDetails.id)) is not monitored by a Segment Server (e.g uses Cloud Connector, Lightweight Agent). Only hosts monitored by a Segment Server can be pinned to a deployment cluster." + exit 1 + } + # 2nd: Check if asset is healthy, if not, error out. + if ($AssetDetails.healthState.healthStatus -ne 1) { + Write-Error "Asset $($AssetDetails.name) ($($AssetDetails.id)) is not healthy! Please check the asset health in the portaland try again." + exit 1 + } + # 3rd: Check if asset is already pinned to a deployment cluster, if so, error out. + # deploymentsClusterSource = 5 --> NONE. + if ($AssetDetails.deploymentsClusterSource -ne 5 -and $AssetDetails.deploymentsClusterSource -ne 6) { + Write-Error "Asset $($AssetDetails.name) ($($AssetDetails.id)) is already pinned to Deployment Cluster ID: $($AssetDetails.deploymentsCluster.id) - Deployment Cluster Name: $($AssetDetails.deploymentsCluster.name) - Segment Server ID: $($AssetDetails.assignedDeployment.id) - Segment Server Name: $($AssetDetails.assignedDeployment.name)" + exit 1 + } + # 4th: If asset is not applicable to be pinned to a deployment cluster, error out. + # deploymentsClusterSource = 6 --> NOT_APPLICABLE. + elseif ($AssetDetails.deploymentsClusterSource -eq 6) { + Write-Error "Asset $($AssetDetails.name) ($($AssetDetails.id)) applicable to be pinned to a deployment cluster!" + exit 1 + } + Write-Host "Validated that asset $($AssetDetails.name) ($($AssetDetails.id)) can be pinned to deployment cluster: $($script:DeploymentClusterHashtable[$DeploymentClusterId].name)" +} + +function Get-AssetDetails { + param( + [Parameter(Mandatory = $true)] + [string]$AssetId + ) + Write-Host "Getting asset details for asset ID: $AssetId" + try { + $response = Invoke-ApiRequest -Method "GET" -ApiEndpoint "assets/$AssetId" + if ($null -eq $response.entity) { + Write-Error "Asset details response is malformed and does not contain 'entity' property" + exit 1 + } + Write-Host "Found asset details for $($response.entity.name) - $AssetId" + return $response.entity + } + catch { + if ($null -ne $_.Exception.StatusCode -and ($_.Exception.StatusCode -eq 404)) { + Write-Error "Asset with ID $AssetId was not found!" + exit 1 + } + else { + Write-Error $_ + exit 1 + } + } +} + +function Set-AssetsToDeploymentCluster { + param( + [Parameter(Mandatory = $true)] + [array]$AssetIdsArray, + [Parameter(Mandatory = $true)] + [string]$DeploymentClusterId + ) + $body = @{ + assetIds = $AssetIdsArray + deploymentsClusterId = $DeploymentClusterId + } + Write-Host "Pinning $($AssetIdsArray.Count) assets to deployment cluster: $($script:DeploymentClusterHashtable[$DeploymentClusterId].name)" + Invoke-ApiRequest -Method "PUT" -ApiEndpoint "/assets/actions/deployments-cluster" -Body $body +} + +<# +This section of the script contains functions related to +deployment cluster operations. +#> +function Get-DeploymentClusters { + Write-Host "Getting deployment clusters!" + $response = Invoke-ApiRequest -Method "GET" -ApiEndpoint "environments/cluster" + if (-not $response.items) { + Write-Error "Deployment clusters response is malformed and does not contain 'items' property" + exit 1 + } + if ($response.items.Count -eq 0) { + Write-Error "No deployment clusters found!" + exit 1 + } + if ($response.items -isnot [System.Array]) { + $DeploymentClusters = @($response.items) + } + else { + $DeploymentClusters = $response.items + } + + # Test if DeploymentClusterFieldMappings.json file exists, if so, read it into a hashtable + if (Test-Path -Path "DeploymentClusterFieldMappings.json") { + $DeploymentClusters = Invoke-DecodeDeploymentClusterIDFields -DeploymentClusters $DeploymentClusters + } + else { + Write-Warning "DeploymentClusterFieldMappings.json file not found! Skipping deployment cluster ID field decoding!" + } + + # Create a script-wide hashtable of deployment clusters for easy access and validation + New-DeploymentClusterHashtable -DeploymentClusters $DeploymentClusters + + return $DeploymentClusters +} +function New-DeploymentClusterHashtable { + param( + [Parameter(Mandatory = $true)] + [System.Array]$DeploymentClusters + ) + $script:DeploymentClusterHashtable = @{} + foreach ($cluster in $DeploymentClusters){ + $DeploymentClusterHashtable[$cluster.id] = $cluster + } + Write-Host "Created script-wide hashtable of deployment clusters!" + +} +function Invoke-DecodeDeploymentClusterIDFields { + param( + [Parameter(Mandatory = $true)] + [System.Array]$DeploymentClusters + ) + Write-Host "Decoding deployment cluster ID fields! (e.g Updating strategy=2 --> strategy=Active/Active)" + $DeploymentClusterFieldMappings = Get-Content -Path "DeploymentClusterFieldMappings.json" | ConvertFrom-Json -AsHashtable -Depth 10 + foreach ($cluster in $DeploymentClusters){ + if ($cluster.strategy.GetType() -eq [System.Int64]) { + $cluster.strategy = $DeploymentClusterFieldMappings['strategy']['byId'][$cluster.strategy.ToString()] + } + + if ($cluster.assignedDeployments.Count -gt 0) { + foreach ($deployment in $cluster.assignedDeployments){ + if ($deployment.status.GetType() -eq [System.Int64]) { + $deployment.status = $DeploymentClusterFieldMappings['assignedDeployments.status']['byId'][$deployment.status.ToString()] + } + if ($deployment.state.GetType() -eq [System.Int64]) { + $deployment.state = $DeploymentClusterFieldMappings['assignedDeployments.state']['byId'][$deployment.state.ToString()] + } + if ($deployment.servicesInfo.Count -gt 0) { + foreach ($service in $deployment.servicesInfo){ + if ($service.serviceId.GetType() -eq [System.Int64]) { + $service.serviceId = $DeploymentClusterFieldMappings['assignedDeployments.servicesInfo.serviceId']['byId'][$service.serviceId.ToString()] + } + if ($service.status.GetType() -eq [System.Int64]) { + $service.status = $DeploymentClusterFieldMappings['assignedDeployments.servicesInfo.status']['byId'][$service.status.ToString()] + } + if ($service.state.GetType() -eq [System.Int64]) { + $service.state = $DeploymentClusterFieldMappings['assignedDeployments.servicesInfo.state']['byId'][$service.state.ToString()] + } + } + } + } + } + } + return $DeploymentClusters +} + +function Write-DeploymentClusters { + param( + [Parameter(Mandatory = $true)] + [object]$DeploymentClusters + ) + + Write-Host "Writing deployment clusters information to console!" + foreach ($cluster in $DeploymentClusters){ + Write-Host $("="*(($Host.UI.RawUI.WindowSize.Width)/2)) + Write-Host "Deployment cluster: $($cluster.name)" + Write-Host "Cluster ID: $($cluster.id)" + Write-Host "Number of assets in cluster: $($cluster.numOfAssets)" + Write-Host "HA Strategy: $(if ($cluster.strategy -eq 2) { "Active/Active" } else { "Active/Passive" })" + Write-Host "Segment server deployments assigned to this cluster:" + if ($cluster.assignedDeployments.Count -eq 0) { + Write-Host "$(" "*2)No segment server deploments are assigned to this cluster!" + } + else { + Write-Host "$("~"*(($Host.UI.RawUI.WindowSize.Width)/4))" + foreach ($deployment in $cluster.assignedDeployments){ + Write-Host "$(" "*2)Name: $($deployment.name)" + Write-Host "$(" "*2)Deployment ID: $($deployment.id)" + Write-Host "$(" "*2)Server Asset ID: $($deployment.assetId)" + Write-Host "$(" "*2)Status: $($deployment.status)" + Write-Host "$(" "*2)State: $($deployment.state)" + Write-Host "$(" "*2)Num Assets Associated: $($deployment.numOfAssets)" + Write-Host "$(" "*2)Internal IP Address: $($deployment.internalIpAddress)" + Write-Host "$(" "*2)External IP Address: $($deployment.externalIpAddress)" + Write-Host "$(" "*2)Segment Server Version: $($deployment.assemblyVersion)" + Write-Host "$(" "*2)Is Preferred Deployment: $(if ($deployment.id -eq $cluster.preferredDeployment.id) { "Yes" } else { "No" })" + Write-Host "$(" "*2)Deployment Services:" + foreach ($service in $deployment.servicesInfo){ + Write-Host "$(" "*4)--------------------------------" + Write-Host "$(" "*4)Service ID: $($service.serviceId)" + Write-Host "$(" "*4)Service Status: $($service.status)" + Write-Host "$(" "*4)Service State: $($service.state)" + Write-Host "$(" "*4)--------------------------------" + } + + Write-Host "$(" "*2)$("-"*(($Host.UI.RawUI.WindowSize.Width)/4))" + } + } + Write-Host $("="*(($Host.UI.RawUI.WindowSize.Width)/2)) + } + Write-Host "Finished writing deployment clusters information to console!" + +} + +<# +This section of the script is responsible for +creating and exporting the CSV template. +#> function Export-CsvTemplate { $template = [PSCustomObject]@{ AssetName = $null @@ -115,61 +331,88 @@ function Get-CsvData { return $csvData } -function Get-DeploymentClusters { - Write-Host "Getting deployment clusters!" - $response = Invoke-ApiRequest -Method "GET" -ApiEndpoint "environments/cluster" - if (-not $response.items) { - Write-Error "Deployment clusters response is malformed and does not contain 'items' property" - exit 1 - } - if ($response.items.Count -eq 0) { - Write-Error "No deployment clusters found!" - exit 1 - } - if ($response.items -isnot [System.Array]) { - return @($response.items) - } - else { - return $response.items +<# +This section of the script is responsible for +initializing the API context and making API requests. +#> +function Initialize-ApiContext { + $script:Headers = @{ + Accept = "application/json" + Authorization = $ApiKey } + $script:ApiBaseUrl = "$PortalUrl/api/v1" } -function Write-DeploymentClusters { +function Test-ApiResponseStatusCode { param( [Parameter(Mandatory = $true)] - [object]$DeploymentClusters + [int]$StatusCode, + + [Parameter(Mandatory = $false)] + [object]$Response = $null ) - foreach ($cluster in $DeploymentClusters){ - Write-Host $("="*($Host.UI.RawUI.WindowSize.Width)) - Write-Host "Deployment cluster: $($cluster.name)" - Write-Host "Cluster ID: $($cluster.id)" - Write-Host "Number of assets in cluster: $($cluster.numOfAssets)" - Write-Host "HA Strategy: $(if ($cluster.strategy -eq 2) { "Active/Active" } else { "Active/Passive" })" - Write-Host "Segment server deployments assigned to this cluster:" - if ($cluster.assignedDeployments.Count -eq 0) { - Write-Host "$("`t"*1)No segment server deploments are assigned to this cluster!" + + # First check if status code is 2XX (success) + if ($StatusCode -ge 200 -and $StatusCode -lt 300) { + return + } + + # Define specific error status codes with their reason phrases + $errorStatusCodes = @{ + 400 = "Bad Request" + 401 = "Unauthorized" + 403 = "Forbidden" + 404 = "Not Found" + 405 = "Method Not Allowed" + 500 = "Internal Server Error" + 501 = "Not Implemented" + 503 = "Service Unavailable" + } + + # Determine error message based on whether status code is in the defined list + if ($errorStatusCodes.ContainsKey($StatusCode)) { + $reasonPhrase = $errorStatusCodes[$StatusCode] + $errorMessage = "API request failed with status code $StatusCode ($reasonPhrase)" + } + else { + # Catch-all for any other non-2XX status code + $errorMessage = "API request failed with status code $StatusCode" + } + + # Format response body if available + $responseBody = $null + if ($null -ne $Response) { + $responseBody = if ($Response -is [string]) { + $Response + } + elseif ($Response -is [PSCustomObject] -or $Response -is [hashtable]) { + $Response | ConvertTo-Json -Depth 10 } else { - foreach ($deployment in $cluster.assignedDeployments){ - Write-Host "--------------------------------" - Write-Host "$("`t"*1)Name: $($deployment.name)" - Write-Host "$("`t"*1)Deployment ID: $($deployment.id)" - Write-Host "$("`t"*1)Deployment IP Address: $($deployment.internalIpAddress)" - Write-Host "$("`t"*1)Is Preferred Deployment: $(if ($deployment.id -eq $cluster.preferredDeployment.id) { "Yes" } else { "No" })" - Write-Host "--------------------------------" - } + $Response.ToString() } - Write-Host $("="*($Host.UI.RawUI.WindowSize.Width)) - } -} - - -function Initialize-ApiContext { - $script:Headers = @{ - Accept = "application/json" - Authorization = $ApiKey } - $script:ApiBaseUrl = "$PortalUrl/api/v1" + + # Build full error message with response body if available + $fullErrorMessage = $errorMessage + if ($null -ne $responseBody -and $responseBody.Trim() -ne "") { + $fullErrorMessage = "$errorMessage`nResponse body: $responseBody" + } + + # Create exception with status code and response as attributes + $exception = New-Object System.Exception $fullErrorMessage + $exception | Add-Member -MemberType NoteProperty -Name "StatusCode" -Value $StatusCode + $exception | Add-Member -MemberType NoteProperty -Name "Response" -Value $Response + + # Create error record and throw + $errorRecord = New-Object System.Management.Automation.ErrorRecord( + $exception, + "ApiRequestFailed", + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $null + ) + + throw $errorRecord } function Invoke-ApiRequest { @@ -198,34 +441,38 @@ function Invoke-ApiRequest { $Body } else { - $Body | ConvertTo-Json -Depth 10 + $Body | ConvertTo-Json -Depth 10 -Compress } $requestParams['ContentType'] = "application/json" } - - $response = Invoke-RestMethod @requestParams + $statusCode = $null + $response = Invoke-RestMethod @requestParams -SkipHttpErrorCheck -StatusCodeVariable statusCode + + # Check for error status codes and handle accordingly + Test-ApiResponseStatusCode -StatusCode $statusCode -Response $response + return $response } catch { - $statusCode = $null - if ($_.Exception.Response) { - $statusCode = [int]$_.Exception.Response.StatusCode - } - - if ($null -ne $statusCode -and $statusCode -ge 400 -and $statusCode -lt 500) { - Write-Error "API request failed: The API key is invalid or you do not have permission to access this resource." - exit 1 - } - else { - Write-Error "API request failed: $_" - exit 1 + if ($null -ne $_.Exception.StatusCode -and ($_.Exception.StatusCode -eq 404)) { + throw $_ } + Write-Error "API request to $($requestParams['Uri']) failed due to error:`n$_" + exit 1 } } +<# +This is the main switch statement that determines which +workflow to execute based on the parameter set matched. +#> switch ($PSCmdlet.ParameterSetName) { "ByAssetId" { + Write-Host "Starting workflow to pin asset $AssetId to deployment cluster $DeploymentClusterId" Initialize-ApiContext + $AssetDetails = Get-AssetDetails -AssetId $AssetId + Test-AssetCanBePinned -AssetDetails $AssetDetails + Set-AssetsToDeploymentCluster -AssetIdsArray @($AssetId) -DeploymentClusterId $DeploymentClusterId "" } "ByCsvPath" { diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/TODO b/Segment/Segment/Asset Management/Pin Assets To Clusters/TODO index 9347531..9eb8979 100644 --- a/Segment/Segment/Asset Management/Pin Assets To Clusters/TODO +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/TODO @@ -1,3 +1,2 @@ -# TODO - Add function to validate if get asset details from API - check if asset is pinned to deployment alredy -# TODO - Add function to pin assets to deployment +# TODO - Fix permission error in pinning function # TODO - Add function to unpin assets from deployment - validate they are pinned first \ No newline at end of file From 32e0748182f079b58e73f9d298458abc1006f1ff Mon Sep 17 00:00:00 2001 From: Thomas Obarowski Date: Mon, 22 Dec 2025 14:40:09 -0500 Subject: [PATCH 6/6] (feat): Finished initial development. Script can export csv template, list deployment clusters, pin/unpin single assets, pin/unpin bulk assets via csv --- .../Pin Assets To Clusters/.gitignore | 2 +- .../AssetDetailsFieldMappings.json | 1197 ----------------- .../Pin-AssetsToClusters.ps1 | 384 +++++- .../Pin Assets To Clusters/README.md | 332 +++++ 4 files changed, 644 insertions(+), 1271 deletions(-) delete mode 100644 Segment/Segment/Asset Management/Pin Assets To Clusters/AssetDetailsFieldMappings.json create mode 100644 Segment/Segment/Asset Management/Pin Assets To Clusters/README.md diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore b/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore index 1a96327..efaab03 100644 --- a/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/.gitignore @@ -1,4 +1,4 @@ *.csv .env .env.ps1 - +AssetDetailsFieldMappings.json diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/AssetDetailsFieldMappings.json b/Segment/Segment/Asset Management/Pin Assets To Clusters/AssetDetailsFieldMappings.json deleted file mode 100644 index c73186c..0000000 --- a/Segment/Segment/Asset Management/Pin Assets To Clusters/AssetDetailsFieldMappings.json +++ /dev/null @@ -1,1197 +0,0 @@ -{ - "assetType": { - "byId": { - "0": "Undetermined", - "1": "Client", - "2": "Server", - "3": "Virtual cluster", - "4": "IP camera", - "5": "Smart TV", - "6": "Factory controller", - "7": "Medical device", - "8": "Printer", - "9": "Scanner", - "10": "Smart card reader", - "11": "Router", - "12": "Hypervisor", - "13": "PLC", - "14": "HMI", - "15": "Switch", - "16": "Terminal station", - "17": "RTU", - "18": "Wireless access point", - "19": "Historian", - "20": "Game console", - "21": "Fire alarm", - "22": "UPS", - "23": "Storage appliance", - "24": "Virtualization appliance", - "25": "Firewall appliance", - "26": "Security scanner", - "27": "Security controller", - "28": "Door lock", - "29": "Biometric scanner", - "30": "HVAC", - "31": "Room scheduler", - "32": "Load Balancer Appliance", - "33": "WAN Concentrator", - "34": "IPAM Appliance", - "35": "Temperature sensor gateways", - "36": "Power meters", - "37": "Conveyor systems", - "38": "Building automation devices", - "39": "Vision controllers", - "40": "Manufacturing execution systems", - "41": "BACnet broadcast management devices (BBMD)", - "42": "BACnet routers & BBMD", - "43": "Clocks", - "44": "RFID", - "45": "Scales", - "46": "Mobile printers", - "47": "IED", - "48": "CNC mills", - "49": "Room monitors", - "50": "Smart speakers", - "51": "Vending machines", - "52": "Autonomous vehicles", - "53": "Fleet management systems", - "54": "BACnet routers", - "55": "Motor controllers", - "56": "OT gateways", - "57": "Controllers", - "58": "Building management systems", - "59": "VoIP phones", - "60": "Vision sensors", - "61": "Remote I/O", - "62": "VoIP servers", - "63": "Flow meters", - "64": "Building automation controllers", - "65": "Security cameras", - "66": "Digital signs", - "67": "Remote access gateways", - "68": "Temperature sensors", - "69": "RTLS devices", - "70": "Serial-to-Ethernet devices", - "71": "Intercoms", - "72": "Engineering stations", - "73": "Data logger gateways", - "74": "Robot", - "75": "Tablet", - "76": "SCADA Server", - "77": "Industrial Printer", - "78": "Vision Camera", - "79": "Time Clock", - "80": "Video Conference", - "81": "Electrical Drive", - "82": "Access Control", - "83": "Printer 3D", - "84": "Access Control Gateway", - "85": "ATM", - "86": "Audio Decoder", - "87": "AV System", - "88": "BACnet Router BBMD", - "89": "Badge Reader", - "90": "Barcode Scanner", - "91": "Building Automation Controller Gateway", - "92": "Building Management Sensor", - "93": "Building Management Sensor Gateway", - "94": "Building Management System Gateway", - "95": "Car", - "96": "Circuit Monitor", - "97": "Clinical Mobile Device", - "98": "Clinical Mobile Device Gateway", - "99": "Clock Gateway", - "100": "CNC System", - "101": "Conference Room", - "102": "Data Logger", - "103": "Digital Sign Workstation", - "104": "Disk Publisher", - "105": "Dispatcher", - "106": "Embedded PC", - "107": "Encoder", - "108": "EV Charging", - "109": "Fitness Device", - "110": "Generic Mobile Device", - "111": "GPS Navigator", - "112": "Industrial Barcode Scanner", - "113": "Industrial Metal Detector", - "114": "Industrial Network Equipment", - "115": "Industrial Thin Client", - "116": "Industrial Thin Client Gateway", - "117": "Industrial Wireless", - "118": "Industrial Workstation", - "119": "Inspection System", - "120": "Interactive Voice Response", - "121": "Intercom Gateway", - "122": "IoT Controller", - "123": "Lock Box", - "124": "Machinery Health Analyzer", - "125": "Mailing System", - "126": "Media Gateway", - "127": "Media Player", - "128": "Media Streamer", - "129": "Microscope", - "130": "Mobile Computer", - "131": "Motor Starter", - "132": "Musical Instrument", - "133": "OPC Server", - "134": "Order Fulfillment System", - "135": "OT Device", - "136": "OT Server", - "137": "PA System", - "138": "Payment Kiosk", - "139": "Physical Security Platform", - "140": "Point of Sale", - "141": "Point of Sale Gateway", - "142": "Power Distribution Unit", - "143": "Power Distribution Unit Gateway", - "144": "Power Supply", - "145": "Print Server", - "146": "Projector", - "147": "Radio", - "148": "Radio Repeater", - "149": "Recording Server", - "150": "Reporting Server", - "151": "Room Automation Station", - "152": "Room Display", - "153": "RTLS Gateway", - "154": "Safety System", - "155": "SCADA Client", - "156": "Screen Share", - "157": "Security Xray Scanner", - "158": "Sensor", - "159": "Serial-to-Ethernet Gateway", - "160": "Service Trolley", - "161": "Smart Board", - "162": "Smart Home", - "163": "Smart Light", - "164": "Smart Light Gateway", - "165": "Smart Lock Controller", - "166": "Smartphone", - "167": "Smartwatch", - "168": "Solar Energy", - "169": "Time Clock Gateway", - "170": "Video Conference Gateway", - "171": "Video Decoder", - "172": "Video Encoder", - "173": "Video Surveillance Device", - "174": "VoIP Adapter", - "175": "VoIP Phone Gateway", - "176": "Water System", - "177": "Wireless Phone", - "178": "Wireless Phone Gateway", - "1001": "Other OT" - }, - "byName": { - "Undetermined": 0, - "Client": 1, - "Server": 2, - "Virtual cluster": 3, - "IP camera": 4, - "Smart TV": 5, - "Factory controller": 6, - "Medical device": 7, - "Printer": 8, - "Scanner": 9, - "Smart card reader": 10, - "Router": 11, - "Hypervisor": 12, - "PLC": 13, - "HMI": 14, - "Switch": 15, - "Terminal station": 16, - "RTU": 17, - "Wireless access point": 18, - "Historian": 19, - "Game console": 20, - "Fire alarm": 21, - "UPS": 22, - "Storage appliance": 23, - "Virtualization appliance": 24, - "Firewall appliance": 25, - "Security scanner": 26, - "Security controller": 27, - "Door lock": 28, - "Biometric scanner": 29, - "HVAC": 30, - "Room scheduler": 31, - "Load Balancer Appliance": 32, - "WAN Concentrator": 33, - "IPAM Appliance": 34, - "Temperature sensor gateways": 35, - "Power meters": 36, - "Conveyor systems": 37, - "Building automation devices": 38, - "Vision controllers": 39, - "Manufacturing execution systems": 40, - "BACnet broadcast management devices (BBMD)": 41, - "BACnet routers & BBMD": 42, - "Clocks": 43, - "RFID": 44, - "Scales": 45, - "Mobile printers": 46, - "IED": 47, - "CNC mills": 48, - "Room monitors": 49, - "Smart speakers": 50, - "Vending machines": 51, - "Autonomous vehicles": 52, - "Fleet management systems": 53, - "BACnet routers": 54, - "Motor controllers": 55, - "OT gateways": 56, - "Controllers": 57, - "Building management systems": 58, - "VoIP phones": 59, - "Vision sensors": 60, - "Remote I/O": 61, - "VoIP servers": 62, - "Flow meters": 63, - "Building automation controllers": 64, - "Security cameras": 65, - "Digital signs": 66, - "Remote access gateways": 67, - "Temperature sensors": 68, - "RTLS devices": 69, - "Serial-to-Ethernet devices": 70, - "Intercoms": 71, - "Engineering stations": 72, - "Data logger gateways": 73, - "Robot": 74, - "Tablet": 75, - "SCADA Server": 76, - "Industrial Printer": 77, - "Vision Camera": 78, - "Time Clock": 79, - "Video Conference": 80, - "Electrical Drive": 81, - "Access Control": 82, - "Printer 3D": 83, - "Access Control Gateway": 84, - "ATM": 85, - "Audio Decoder": 86, - "AV System": 87, - "BACnet Router BBMD": 88, - "Badge Reader": 89, - "Barcode Scanner": 90, - "Building Automation Controller Gateway": 91, - "Building Management Sensor": 92, - "Building Management Sensor Gateway": 93, - "Building Management System Gateway": 94, - "Car": 95, - "Circuit Monitor": 96, - "Clinical Mobile Device": 97, - "Clinical Mobile Device Gateway": 98, - "Clock Gateway": 99, - "CNC System": 100, - "Conference Room": 101, - "Data Logger": 102, - "Digital Sign Workstation": 103, - "Disk Publisher": 104, - "Dispatcher": 105, - "Embedded PC": 106, - "Encoder": 107, - "EV Charging": 108, - "Fitness Device": 109, - "Generic Mobile Device": 110, - "GPS Navigator": 111, - "Industrial Barcode Scanner": 112, - "Industrial Metal Detector": 113, - "Industrial Network Equipment": 114, - "Industrial Thin Client": 115, - "Industrial Thin Client Gateway": 116, - "Industrial Wireless": 117, - "Industrial Workstation": 118, - "Inspection System": 119, - "Interactive Voice Response": 120, - "Intercom Gateway": 121, - "IoT Controller": 122, - "Lock Box": 123, - "Machinery Health Analyzer": 124, - "Mailing System": 125, - "Media Gateway": 126, - "Media Player": 127, - "Media Streamer": 128, - "Microscope": 129, - "Mobile Computer": 130, - "Motor Starter": 131, - "Musical Instrument": 132, - "OPC Server": 133, - "Order Fulfillment System": 134, - "OT Device": 135, - "OT Server": 136, - "PA System": 137, - "Payment Kiosk": 138, - "Physical Security Platform": 139, - "Point of Sale": 140, - "Point of Sale Gateway": 141, - "Power Distribution Unit": 142, - "Power Distribution Unit Gateway": 143, - "Power Supply": 144, - "Print Server": 145, - "Projector": 146, - "Radio": 147, - "Radio Repeater": 148, - "Recording Server": 149, - "Reporting Server": 150, - "Room Automation Station": 151, - "Room Display": 152, - "RTLS Gateway": 153, - "Safety System": 154, - "SCADA Client": 155, - "Screen Share": 156, - "Security Xray Scanner": 157, - "Sensor": 158, - "Serial-to-Ethernet Gateway": 159, - "Service Trolley": 160, - "Smart Board": 161, - "Smart Home": 162, - "Smart Light": 163, - "Smart Light Gateway": 164, - "Smart Lock Controller": 165, - "Smartphone": 166, - "Smartwatch": 167, - "Solar Energy": 168, - "Time Clock Gateway": 169, - "Video Conference Gateway": 170, - "Video Decoder": 171, - "Video Encoder": 172, - "Video Surveillance Device": 173, - "VoIP Adapter": 174, - "VoIP Phone Gateway": 175, - "Water System": 176, - "Wireless Phone": 177, - "Wireless Phone Gateway": 178, - "Other OT": 1001, - "ASSET_TYPE_UNKNOWN": 0, - "CLIENT": 1, - "SERVER": 2, - "CLUSTER": 3, - "CAMERA": 4, - "TV": 5, - "FACTORY_CONTROLLER": 6, - "MEDICAL_DEVICE": 7, - "PRINTER": 8, - "SCANNER": 9, - "SMART_CARD_READER": 10, - "ROUTER": 11, - "HYPERVISOR": 12, - "PLC": 13, - "HMI": 14, - "SWITCH": 15, - "TERMINAL_STATION": 16, - "RTU": 17, - "WIRELESS_ACCESS_POINT": 18, - "HISTORIAN": 19, - "GAME_CONSOLE": 20, - "FIRE_ALARM": 21, - "UPS": 22, - "STORAGE_APPLIANCE": 23, - "VIRTUALIZATION_APPLIANCE": 24, - "FIREWALL_APPLIANCE": 25, - "SECURITY_SCANNER": 26, - "SECURITY_CONTROLLER": 27, - "DOOR_LOCK": 28, - "BIOMETRIC_SCANNER": 29, - "HVAC": 30, - "ROOM_SCHEDULER": 31, - "LOAD_BALANCER_APPLIANCE": 32, - "WAN_CONCENTRATOR": 33, - "IPAM_APPLIANCE": 34, - "TEMPERATURE_SENSOR_GATEWAY": 35, - "POWER_METER": 36, - "CONVEYOR_SYSTEM": 37, - "BUILDING_AUTOMATION_DEVICE": 38, - "VISION_CONTROLLER": 39, - "MANUFACTURING_EXECUTION_SYSTEM": 40, - "BACNET_BROADCAST_MANAGEMENT_DEVICE_BBMD": 41, - "BACNET_ROUTER_AND_BBMD": 42, - "CLOCK": 43, - "RFID": 44, - "SCALE": 45, - "MOBILE_PRINTER": 46, - "IED": 47, - "CNC_MILL": 48, - "ROOM_MONITOR": 49, - "SMART_SPEAKER": 50, - "VENDING_MACHINE": 51, - "AUTONOMOUS_VEHICLE": 52, - "FLEET_MANAGEMENT_SYSTEM": 53, - "BACNET_ROUTER": 54, - "MOTOR_CONTROLLER": 55, - "OT_GATEWAY": 56, - "CONTROLLER": 57, - "BUILDING_MANAGEMENT_SYSTEM": 58, - "VOIP_PHONE": 59, - "VISION_SENSOR": 60, - "REMOTE_IO": 61, - "VOIP_SERVER": 62, - "FLOW_METER": 63, - "BUILDING_AUTOMATION_CONTROLLER": 64, - "SECURITY_CAMERA": 65, - "DIGITAL_SIGN": 66, - "REMOTE_ACCESS_GATEWAY": 67, - "TEMPERATURE_SENSOR": 68, - "RTLS": 69, - "SERIAL_TO_ETHERNET": 70, - "INTERCOM": 71, - "ENGINEERING_STATION": 72, - "DATA_LOGGER_GATEWAY": 73, - "ROBOT": 74, - "TABLET": 75, - "SCADA_SERVER": 76, - "INDUSTRIAL_PRINTER": 77, - "VISION_CAMERA": 78, - "TIME_CLOCK": 79, - "VIDEO_CONFERENCE": 80, - "ELECTRICAL_DRIVE": 81, - "ACCESS_CONTROL": 82, - "PRINTER_3D": 83, - "ACCESS_CONTROL_GATEWAY": 84, - "ATM": 85, - "AUDIO_DECODER": 86, - "AV_SYSTEM": 87, - "BACNET_ROUTER_BBMD": 88, - "BADGE_READER": 89, - "BARCODE_SCANNER": 90, - "BUILDING_AUTOMATION_CONTROLLER_GATEWAY": 91, - "BUILDING_MANAGEMENT_SENSOR": 92, - "BUILDING_MANAGEMENT_SENSOR_GATEWAY": 93, - "BUILDING_MANAGEMENT_SYSTEM_GATEWAY": 94, - "CAR": 95, - "CIRCUIT_MONITOR": 96, - "CLINICAL_MOBILE_DEVICE": 97, - "CLINICAL_MOBILE_DEVICE_GATEWAY": 98, - "CLOCK_GATEWAY": 99, - "CNC_SYSTEM": 100, - "CONFERENCE_ROOM": 101, - "DATA_LOGGER": 102, - "DIGITAL_SIGN_WORKSTATION": 103, - "DISK_PUBLISHER": 104, - "DISPATCHER": 105, - "EMBEDDED_PC": 106, - "ENCODER": 107, - "EV_CHARGING": 108, - "FITNESS_DEVICE": 109, - "GENERIC_MOBILE_DEVICE": 110, - "GPS_NAVIGATOR": 111, - "INDUSTRIAL_BARCODE_SCANNER": 112, - "INDUSTRIAL_METAL_DETECTOR": 113, - "INDUSTRIAL_NETWORK_EQUIPMENT": 114, - "INDUSTRIAL_THIN_CLIENT": 115, - "INDUSTRIAL_THIN_CLIENT_GATEWAY": 116, - "INDUSTRIAL_WIRELESS": 117, - "INDUSTRIAL_WORKSTATION": 118, - "INSPECTION_SYSTEM": 119, - "INTERACTIVE_VOICE_RESPONSE": 120, - "INTERCOM_GATEWAY": 121, - "IOT_CONTROLLER": 122, - "LOCK_BOX": 123, - "MACHINERY_HEALTH_ANALYZER": 124, - "MAILING_SYSTEM": 125, - "MEDIA_GATEWAY": 126, - "MEDIA_PLAYER": 127, - "MEDIA_STREAMER": 128, - "MICROSCOPE": 129, - "MOBILE_COMPUTER": 130, - "MOTOR_STARTER": 131, - "MUSICAL_INSTRUMENT": 132, - "OPC_SERVER": 133, - "ORDER_FULFILLMENT_SYSTEM": 134, - "OT_DEVICE": 135, - "OT_SERVER": 136, - "PA_SYSTEM": 137, - "PAYMENT_KIOSK": 138, - "PHYSICAL_SECURITY_PLATFORM": 139, - "POINT_OF_SALE": 140, - "POINT_OF_SALE_GATEWAY": 141, - "POWER_DISTRIBUTION_UNIT": 142, - "POWER_DISTRIBUTION_UNIT_GATEWAY": 143, - "POWER_SUPPLY": 144, - "PRINT_SERVER": 145, - "PROJECTOR": 146, - "RADIO": 147, - "RADIO_REPEATER": 148, - "RECORDING_SERVER": 149, - "REPORTING_SERVER": 150, - "ROOM_AUTOMATION_STATION": 151, - "ROOM_DISPLAY": 152, - "RTLS_GATEWAY": 153, - "SAFETY_SYSTEM": 154, - "SCADA_CLIENT": 155, - "SCREEN_SHARE": 156, - "SECURITY_XRAY_SCANNER": 157, - "SENSOR": 158, - "SERIAL_TO_ETHERNET_GATEWAY": 159, - "SERVICE_TROLLEY": 160, - "SMART_BOARD": 161, - "SMART_HOME": 162, - "SMART_LIGHT": 163, - "SMART_LIGHT_GATEWAY": 164, - "SMART_LOCK_CONTROLLER": 165, - "SMARTPHONE": 166, - "SMARTWATCH": 167, - "SOLAR_ENERGY": 168, - "TIME_CLOCK_GATEWAY": 169, - "VIDEO_CONFERENCE_GATEWAY": 170, - "VIDEO_DECODER": 171, - "VIDEO_ENCODER": 172, - "VIDEO_SURVEILLANCE_DEVICE": 173, - "VOIP_ADAPTER": 174, - "VOIP_PHONE_GATEWAY": 175, - "WATER_SYSTEM": 176, - "WIRELESS_PHONE": 177, - "WIRELESS_PHONE_GATEWAY": 178, - "OTHER_OT": 1001 - } - }, - "protectionState": { - "byId": { - "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", - "1": "Not segmented", - "2": "Unsegmenting", - "3": "Segmented", - "4": "Segmenting", - "5": "In learning", - "6": "Not segmented (manual)", - "7": "Unsegmenting (manual)", - "8": "Segmented (policy)", - "9": "Segmenting (policy)", - "10": "In learning (policy)", - "11": "Learning done", - "12": "Learning (policy) done", - "13": "Enforcing block rules", - "14": "Enforcing block rules (policy)", - "15": "In learning with blocks", - "16": "In learning with blocks (policy)", - "17": "Learning with blocks done", - "18": "Learning with blocks (policy) done" - }, - "byName": { - "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, - "Not segmented": 1, - "Unsegmenting": 2, - "Segmented": 3, - "Segmenting": 4, - "In learning": 5, - "Not segmented (manual)": 6, - "Unsegmenting (manual)": 7, - "Segmented (policy)": 8, - "Segmenting (policy)": 9, - "In learning (policy)": 10, - "Learning done": 11, - "Learning (policy) done": 12, - "Enforcing block rules": 13, - "Enforcing block rules (policy)": 14, - "In learning with blocks": 15, - "In learning with blocks (policy)": 16, - "Learning with blocks done": 17, - "Learning with blocks (policy) done": 18, - "ASSET_PROTECTION_STATE_UNPROTECTED": 1, - "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, - "ASSET_PROTECTION_STATE_PROTECTED": 3, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, - "ASSET_PROTECTION_STATE_QUEUED": 5, - "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, - "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, - "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, - "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 - } - }, - "identityProtectionState": { - "byId": { - "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", - "1": "Not segmented", - "2": "Unsegmenting", - "3": "Segmented", - "4": "Segmenting", - "5": "In learning", - "6": "Not segmented (manual)", - "7": "Unsegmenting (manual)", - "8": "Segmented (policy)", - "9": "Segmenting (policy)", - "10": "In learning (policy)", - "11": "Learning done", - "12": "Learning (policy) done", - "13": "Enforcing block rules", - "14": "Enforcing block rules (policy)", - "15": "In learning with blocks", - "16": "In learning with blocks (policy)", - "17": "Learning with blocks done", - "18": "Learning with blocks (policy) done" - }, - "byName": { - "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, - "Not segmented": 1, - "Unsegmenting": 2, - "Segmented": 3, - "Segmenting": 4, - "In learning": 5, - "Not segmented (manual)": 6, - "Unsegmenting (manual)": 7, - "Segmented (policy)": 8, - "Segmenting (policy)": 9, - "In learning (policy)": 10, - "Learning done": 11, - "Learning (policy) done": 12, - "Enforcing block rules": 13, - "Enforcing block rules (policy)": 14, - "In learning with blocks": 15, - "In learning with blocks (policy)": 16, - "Learning with blocks done": 17, - "Learning with blocks (policy) done": 18, - "ASSET_PROTECTION_STATE_UNPROTECTED": 1, - "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, - "ASSET_PROTECTION_STATE_PROTECTED": 3, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, - "ASSET_PROTECTION_STATE_QUEUED": 5, - "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, - "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, - "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, - "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 - } - }, - "rpcProtectionState": { - "byId": { - "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", - "1": "Not segmented", - "2": "Unsegmenting", - "3": "Segmented", - "4": "Segmenting", - "5": "In learning", - "6": "Not segmented (manual)", - "7": "Unsegmenting (manual)", - "8": "Segmented (policy)", - "9": "Segmenting (policy)", - "10": "In learning (policy)", - "11": "Learning done", - "12": "Learning (policy) done", - "13": "Enforcing block rules", - "14": "Enforcing block rules (policy)", - "15": "In learning with blocks", - "16": "In learning with blocks (policy)", - "17": "Learning with blocks done", - "18": "Learning with blocks (policy) done" - }, - "byName": { - "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, - "Not segmented": 1, - "Unsegmenting": 2, - "Segmented": 3, - "Segmenting": 4, - "In learning": 5, - "Not segmented (manual)": 6, - "Unsegmenting (manual)": 7, - "Segmented (policy)": 8, - "Segmenting (policy)": 9, - "In learning (policy)": 10, - "Learning done": 11, - "Learning (policy) done": 12, - "Enforcing block rules": 13, - "Enforcing block rules (policy)": 14, - "In learning with blocks": 15, - "In learning with blocks (policy)": 16, - "Learning with blocks done": 17, - "Learning with blocks (policy) done": 18, - "ASSET_PROTECTION_STATE_UNPROTECTED": 1, - "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, - "ASSET_PROTECTION_STATE_PROTECTED": 3, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, - "ASSET_PROTECTION_STATE_QUEUED": 5, - "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, - "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, - "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, - "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 - } - }, - "assetStatus": { - "byId": { - "0": "ASSET_STATUS_UNSPECIFIED", - "1": "Not monitored", - "2": "Segment server", - "4": "Can't be monitored (unsupported OS)", - "5": "Can't be monitored (unmonitorable)", - "6": "Can't be monitored (unmonitorable)", - "7": "Cloud connector", - "8": "Not monitored (ansible unreachable)", - "9": "Not monitored (cloud connector uninstalled)", - "10": "Not monitored (cloud connector required)", - "12": "Can't be monitored (inactive entity)", - "13": "Segment server", - "14": "Segment connector" - }, - "byName": { - "ASSET_STATUS_UNSPECIFIED": 0, - "Not monitored": 1, - "Segment server": 2, - "Can't be monitored (unsupported OS)": 4, - "Can't be monitored (unmonitorable)": 5, - "Can't be monitored (unmonitorable)": 6, - "Cloud connector": 7, - "Not monitored (ansible unreachable)": 8, - "Not monitored (cloud connector uninstalled)": 9, - "Not monitored (cloud connector required)": 10, - "Can't be monitored (inactive entity)": 12, - "Segment server": 13, - "Segment connector": 14, - "UNDISCOVERED": 1, - "STALKED": 2, - "OS_UNSUPPORTED": 4, - "TYPE_UNSUPPORTED": 5, - "IGNORED": 6, - "AGENT": 7, - "UNREACHABLE": 8, - "AGENT_UNINSTALLED": 9, - "PENDING_AGENT": 10, - "DELETED": 12, - "STALKED_EXTERNALLY": 13, - "LIGHTWEIGHT_AGENT": 14 - } - }, - "osType": { - "byId": { - "0": "OS_TYPE_UNSPECIFIED", - "1": "Unmanageable OS", - "2": "Windows", - "3": "Linux", - "4": "macOS" - }, - "byName": { - "OS_TYPE_UNSPECIFIED": 0, - "Unmanageable OS": 1, - "Windows": 2, - "Linux": 3, - "macOS": 4, - "UNMANAGEABLE": 1, - "WINDOWS": 2, - "LINUX": 3, - "MAC": 4 - } - }, - "source": { - "byId": { - "0": "ENTITY_SOURCE_UNSPECIFIED", - "1": "Access portal", - "2": "Access portal", - "3": "Active directory", - "4": "Custom", - "5": "System", - "6": "Ansible", - "7": "Manual OT/IoT", - "8": "Workgroup", - "9": "Entra ID (Azure AD)", - "10": "Azure", - "11": "AWS", - "12": "GCP", - "13": "Tag", - "14": "Jamf", - "15": "Manual Linux", - "16": "IBM cloud", - "17": "Oracle cloud", - "18": "VMware cloud", - "19": "Alibaba cloud", - "20": "Lumen cloud", - "21": "OVH cloud", - "22": "Connect", - "23": "AI", - "24": "ServiceNow", - "25": "Google workspace", - "26": "OU", - "27": "Environment", - "28": "Conditional", - "29": "Claroty", - "30": "Manual Mac" - }, - "byName": { - "ENTITY_SOURCE_UNSPECIFIED": 0, - "Access portal": 1, - "Active directory": 3, - "Custom": 4, - "System": 5, - "Ansible": 6, - "Manual OT/IoT": 7, - "Workgroup": 8, - "Entra ID (Azure AD)": 9, - "Azure": 10, - "AWS": 11, - "GCP": 12, - "Tag": 13, - "Jamf": 14, - "Manual Linux": 15, - "IBM cloud": 16, - "Oracle cloud": 17, - "VMware cloud": 18, - "Alibaba cloud": 19, - "Lumen cloud": 20, - "OVH cloud": 21, - "Connect": 22, - "AI": 23, - "ServiceNow": 24, - "Google workspace": 25, - "OU": 26, - "Environment": 27, - "Conditional": 28, - "Claroty": 29, - "Manual Mac": 30, - "ENTITY_SOURCE_PORTAL": 1, - "ENTITY_SOURCE_SSP": 2, - "ENTITY_SOURCE_AD": 3, - "ENTITY_SOURCE_CUSTOM": 4, - "ENTITY_SOURCE_SYSTEM": 5, - "ENTITY_SOURCE_ANSIBLE": 6, - "ENTITY_SOURCE_MANUAL_OT": 7, - "ENTITY_SOURCE_WORKGROUP": 8, - "ENTITY_SOURCE_AZURE_AD": 9, - "ENTITY_SOURCE_AZURE": 10, - "ENTITY_SOURCE_AWS": 11, - "ENTITY_SOURCE_GCP": 12, - "ENTITY_SOURCE_TAG": 13, - "ENTITY_SOURCE_JAMF": 14, - "ENTITY_SOURCE_LINUX": 15, - "ENTITY_SOURCE_IBM": 16, - "ENTITY_SOURCE_ORACLE": 17, - "ENTITY_SOURCE_VMWARE": 18, - "ENTITY_SOURCE_ALIBABA": 19, - "ENTITY_SOURCE_LUMEN": 20, - "ENTITY_SOURCE_OVH": 21, - "ENTITY_SOURCE_VPN": 22, - "ENTITY_SOURCE_AI": 23, - "ENTITY_SOURCE_SNOW": 24, - "ENTITY_SOURCE_GOOGLE_WORKSPACE": 25, - "ENTITY_SOURCE_OU": 26, - "ENTITY_SOURCE_ENVIRONMENT": 27, - "ENTITY_SOURCE_CONDITIONAL": 28, - "ENTITY_SOURCE_CLAROTY_OT": 29, - "ENTITY_SOURCE_MANUAL_MAC": 30 - } - }, - "healthState.healthStatus": { - "byId": { - "0": "Unknown", - "1": "Healthy", - "2": "Error", - "3": "Warning", - "4": "N/A", - "5": "Retrying", - "6": "Blocker" - }, - "byName": { - "Unknown": 0, - "Healthy": 1, - "Error": 2, - "Warning": 3, - "N/A": 4, - "Retrying": 5, - "Blocker": 6, - "ASSET_HEALTH_STATUS_UNSPECIFIED": 0, - "HEALTHY": 1, - "ERROR": 2, - "WARNING": 3, - "UNSUPPORTED": 4, - "RETRY": 5, - "BLOCKER": 6 - } - }, - "deploymentsClusterSource": { - "byId": { - "0": "ASSIGNED_ASSET_DEPLOYMENTS_CLUSTER_SOURCE_UNKNOWN", - "1": "SYSTEM", - "2": "USER", - "3": "DOMAIN", - "4": "SUBNET", - "5": "NONE", - "6": "NOT_APPLICABLE" - }, - "byName": { - "ASSIGNED_ASSET_DEPLOYMENTS_CLUSTER_SOURCE_UNKNOWN": 0, - "SYSTEM": 1, - "USER": 2, - "DOMAIN": 3, - "SUBNET": 4, - "NONE": 5, - "NOT_APPLICABLE": 6 - } - }, - "inactiveReason": { - "byId": { - "0": "INACTIVE_REASON_UNSPECIFIED", - "1": "Duplicated in asset repository", - "2": "Manually set as inactive", - "3": "", - "4": "Disabled in asset repository", - "5": "Not active in asset repository", - "6": "Deleted in asset repository" - }, - "byName": { - "INACTIVE_REASON_UNSPECIFIED": 0, - "Duplicated in asset repository": 1, - "Manually set as inactive": 2, - "": 3, - "Disabled in asset repository": 4, - "Not active in asset repository": 5, - "Deleted in asset repository": 6, - "REPO_DUPLICATED": 1, - "MANUAL": 2, - "NONE": 3, - "REPO_DISABLED": 4, - "REPO_INACTIVITY": 5, - "REPO_DELETED": 6 - } - }, - "outboundRestriction": { - "byId": { - "0": "OUTBOUND_RESTRICTION_UNSPECIFIED", - "1": "Outbound is not restricted", - "2": "Outbound internal is restricted", - "3": "Outbound external is restricted", - "4": "Outbound internal and external is restricted", - "5": "NO_APPLICABLE" - }, - "byName": { - "OUTBOUND_RESTRICTION_UNSPECIFIED": 0, - "Outbound is not restricted": 1, - "Outbound internal is restricted": 2, - "Outbound external is restricted": 3, - "Outbound internal and external is restricted": 4, - "NO_APPLICABLE": 5, - "NO_RESTRICTION": 1, - "RESTRICTED_INTERNAL": 2, - "RESTRICTED_EXTERNAL": 3, - "RESTRICTED_INTERNAL_EXTERNAL": 4 - } - }, - "enforcementMethod": { - "byId": { - "0": "N/A", - "1": "IP Tables", - "2": "NF Tables", - "3": "Windows Firewall", - "4": "WFP" - }, - "byName": { - "N/A": 0, - "IP Tables": 1, - "NF Tables": 2, - "Windows Firewall": 3, - "WFP": 4, - "ASSET_FIREWALL_TYPE_UNSPECIFIED": 0, - "LINUX_IPTABLES": 1, - "LINUX_NFTABLES": 2, - "WINDOWS_FIREWALL": 3, - "WINDOWS_WFP": 4 - } - }, - "state.protectionState": { - "byId": { - "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", - "1": "Not segmented", - "2": "Unsegmenting", - "3": "Segmented", - "4": "Segmenting", - "5": "In learning", - "6": "Not segmented (manual)", - "7": "Unsegmenting (manual)", - "8": "Segmented (policy)", - "9": "Segmenting (policy)", - "10": "In learning (policy)", - "11": "Learning done", - "12": "Learning (policy) done", - "13": "Enforcing block rules", - "14": "Enforcing block rules (policy)", - "15": "In learning with blocks", - "16": "In learning with blocks (policy)", - "17": "Learning with blocks done", - "18": "Learning with blocks (policy) done" - }, - "byName": { - "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, - "Not segmented": 1, - "Unsegmenting": 2, - "Segmented": 3, - "Segmenting": 4, - "In learning": 5, - "Not segmented (manual)": 6, - "Unsegmenting (manual)": 7, - "Segmented (policy)": 8, - "Segmenting (policy)": 9, - "In learning (policy)": 10, - "Learning done": 11, - "Learning (policy) done": 12, - "Enforcing block rules": 13, - "Enforcing block rules (policy)": 14, - "In learning with blocks": 15, - "In learning with blocks (policy)": 16, - "Learning with blocks done": 17, - "Learning with blocks (policy) done": 18, - "ASSET_PROTECTION_STATE_UNPROTECTED": 1, - "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, - "ASSET_PROTECTION_STATE_PROTECTED": 3, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, - "ASSET_PROTECTION_STATE_QUEUED": 5, - "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, - "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, - "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, - "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 - } - }, - "state.identityProtectionState": { - "byId": { - "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", - "1": "Not segmented", - "2": "Unsegmenting", - "3": "Segmented", - "4": "Segmenting", - "5": "In learning", - "6": "Not segmented (manual)", - "7": "Unsegmenting (manual)", - "8": "Segmented (policy)", - "9": "Segmenting (policy)", - "10": "In learning (policy)", - "11": "Learning done", - "12": "Learning (policy) done", - "13": "Enforcing block rules", - "14": "Enforcing block rules (policy)", - "15": "In learning with blocks", - "16": "In learning with blocks (policy)", - "17": "Learning with blocks done", - "18": "Learning with blocks (policy) done" - }, - "byName": { - "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, - "Not segmented": 1, - "Unsegmenting": 2, - "Segmented": 3, - "Segmenting": 4, - "In learning": 5, - "Not segmented (manual)": 6, - "Unsegmenting (manual)": 7, - "Segmented (policy)": 8, - "Segmenting (policy)": 9, - "In learning (policy)": 10, - "Learning done": 11, - "Learning (policy) done": 12, - "Enforcing block rules": 13, - "Enforcing block rules (policy)": 14, - "In learning with blocks": 15, - "In learning with blocks (policy)": 16, - "Learning with blocks done": 17, - "Learning with blocks (policy) done": 18, - "ASSET_PROTECTION_STATE_UNPROTECTED": 1, - "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, - "ASSET_PROTECTION_STATE_PROTECTED": 3, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, - "ASSET_PROTECTION_STATE_QUEUED": 5, - "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, - "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, - "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, - "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 - } - }, - "state.rpcProtectionState": { - "byId": { - "0": "ASSET_PROTECTION_STATE_UNSPECIFIED", - "1": "Not segmented", - "2": "Unsegmenting", - "3": "Segmented", - "4": "Segmenting", - "5": "In learning", - "6": "Not segmented (manual)", - "7": "Unsegmenting (manual)", - "8": "Segmented (policy)", - "9": "Segmenting (policy)", - "10": "In learning (policy)", - "11": "Learning done", - "12": "Learning (policy) done", - "13": "Enforcing block rules", - "14": "Enforcing block rules (policy)", - "15": "In learning with blocks", - "16": "In learning with blocks (policy)", - "17": "Learning with blocks done", - "18": "Learning with blocks (policy) done" - }, - "byName": { - "ASSET_PROTECTION_STATE_UNSPECIFIED": 0, - "Not segmented": 1, - "Unsegmenting": 2, - "Segmented": 3, - "Segmenting": 4, - "In learning": 5, - "Not segmented (manual)": 6, - "Unsegmenting (manual)": 7, - "Segmented (policy)": 8, - "Segmenting (policy)": 9, - "In learning (policy)": 10, - "Learning done": 11, - "Learning (policy) done": 12, - "Enforcing block rules": 13, - "Enforcing block rules (policy)": 14, - "In learning with blocks": 15, - "In learning with blocks (policy)": 16, - "Learning with blocks done": 17, - "Learning with blocks (policy) done": 18, - "ASSET_PROTECTION_STATE_UNPROTECTED": 1, - "ASSET_PROTECTION_STATE_REMOVING_PROTECTION": 2, - "ASSET_PROTECTION_STATE_PROTECTED": 3, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION": 4, - "ASSET_PROTECTION_STATE_QUEUED": 5, - "ASSET_PROTECTION_STATE_FORCED_UNPROTECTED": 6, - "ASSET_PROTECTION_STATE_FORCED_REMOVING_PROTECTION": 7, - "ASSET_PROTECTION_STATE_PROTECTED_DUE_TO_POLICY": 8, - "ASSET_PROTECTION_STATE_APPLYING_PROTECTION_DUE_TO_POLICY": 9, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY": 10, - "ASSET_PROTECTION_STATE_QUEUED_DONE": 11, - "ASSET_PROTECTION_STATE_QUEUED_DUE_TO_POLICY_DONE": 12, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS": 13, - "ASSET_PROTECTION_STATE_APPLYING_QUEUE_WITH_BLOCKS_DUE_TO_POLICY": 14, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS": 15, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY": 16, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DONE": 17, - "ASSET_PROTECTION_STATE_QUEUED_WITH_BLOCKS_DUE_TO_POLICY_DONE": 18 - } - } - } \ No newline at end of file diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 b/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 index 391710b..301bbac 100644 --- a/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/Pin-AssetsToClusters.ps1 @@ -25,6 +25,16 @@ param( [Parameter(ParameterSetName = "ByCsvPath")] [switch]$Unpin, + # Shared switch parameter to skip segment server validation (available in all sets with ApiKey) + [Parameter(ParameterSetName = "ByAssetId", Mandatory = $false)] + [Parameter(ParameterSetName = "ByCsvPath", Mandatory = $false)] + [switch]$SkipSegmentServerValidation, + + # Shared switch parameter for dry run mode (available in all sets with ApiKey) + [Parameter(ParameterSetName = "ByAssetId", Mandatory = $false)] + [Parameter(ParameterSetName = "ByCsvPath", Mandatory = $false)] + [switch]$DryRun, + # ParameterSet 2: List Deployment Clusters [Parameter(ParameterSetName = "ListDeploymentClusters", Mandatory = $true)] [switch]$ListDeploymentClusters, @@ -37,7 +47,98 @@ param( [Parameter(ParameterSetName = "ExportCsvTemplate", Mandatory = $true)] [switch]$ExportCsvTemplate ) +$ErrorActionPreference = "Stop" +# Script-wide deployment cluster field mappings hashtable +$script:DeploymentClusterFieldMappings = @{ + "strategy" = @{ + "byId" = @{ + "0" = "CLUSTER_STRATEGY_UNSPECIFIED" + "1" = "Active / Passive" + "2" = "Active / Active" + } + "byName" = @{ + "CLUSTER_STRATEGY_UNSPECIFIED" = 0 + "Active / Passive" = 1 + "Active / Active" = 2 + "ACTIVE_PASSIVE" = 1 + "ACTIVE_ACTIVE" = 2 + } + } + "assignedDeployments.status" = @{ + "byId" = @{ + "0" = "DEPLOYMENT_STATUS_UNSPECIFIED" + "1" = "Offline" + "2" = "Online" + "3" = "Network disconnected" + } + "byName" = @{ + "DEPLOYMENT_STATUS_UNSPECIFIED" = 0 + "Offline" = 1 + "Online" = 2 + "Network disconnected" = 3 + } + } + "assignedDeployments.state" = @{ + "byId" = @{ + "0" = "DEPLOYMENT_STATE_UNSPECIFIED" + "1" = "Primary" + "2" = "Secondary" + } + "byName" = @{ + "DEPLOYMENT_STATE_UNSPECIFIED" = 0 + "Primary" = 1 + "Secondary" = 2 + "DEPLOYMENT_STATE_PRIMARY" = 1 + "DEPLOYMENT_STATE_SECONDARY" = 2 + } + } + "assignedDeployments.servicesInfo.serviceId" = @{ + "byId" = @{ + "0" = "SERVICE_ID_UNSPECIFIED" + "1" = "ad" + "2" = "winrm" + "3" = "ansible-manager" + } + "byName" = @{ + "SERVICE_ID_UNSPECIFIED" = 0 + "ad" = 1 + "winrm" = 2 + "ansible-manager" = 3 + "SERVICE_ID_AD" = 1 + "SERVICE_ID_WINRM" = 2 + "SERVICE_ID_ANSIBLE_MANAGER" = 3 + } + } + "assignedDeployments.servicesInfo.status" = @{ + "byId" = @{ + "0" = "DEPLOYMENT_STATUS_UNSPECIFIED" + "1" = "Offline" + "2" = "Online" + "3" = "Network disconnected" + } + "byName" = @{ + "DEPLOYMENT_STATUS_UNSPECIFIED" = 0 + "Offline" = 1 + "Online" = 2 + "Network disconnected" = 3 + } + } + "assignedDeployments.servicesInfo.state" = @{ + "byId" = @{ + "0" = "DEPLOYMENT_STATE_UNSPECIFIED" + "1" = "Primary" + "2" = "Secondary" + } + "byName" = @{ + "DEPLOYMENT_STATE_UNSPECIFIED" = 0 + "Primary" = 1 + "Secondary" = 2 + "DEPLOYMENT_STATE_PRIMARY" = 1 + "DEPLOYMENT_STATE_SECONDARY" = 2 + } + } +} <# This section of the script contains all of the @@ -46,31 +147,60 @@ asset related functions in the script function Test-AssetCanBePinned { param( [Parameter(Mandatory = $true)] - [object]$AssetDetails + [string]$AssetId, + [Parameter(Mandatory = $false)] + [switch]$AssetMustBePinned ) + # Get asset details from portal API + $AssetDetails = Get-AssetDetails -AssetId $AssetId + + <# + AssetIsPinnedDeploymentClusterStatus: + 0 --> ASSIGNED_ASSET_DEPLOYMENTS_CLUSTER_SOURCE_UNKNOWN + 1 --> SYSTEM + 2 --> USER + 3 --> DOMAIN + 4 --> SUBNET + 5 --> NONE + 6 --> NOT_APPLICABLE + =============================================== + # 0-4 --> Asset is pinned to a deployment cluster + # 5 --> Asset is not pinned to a deployment cluster + # 6 --> Asset is not applicable to be pinned to a deployment cluster + #> + $AssetIsPinnedDeploymentClusterSource = @(0,1,2,3,4) # ORDER OF VALIDATION MATTERS HERE! # 1st: Check if asset is monitored by a Segment Server, if not, error out. if ($AssetDetails.assetStatus -ne 2) { - Write-Error "Asset $($AssetDetails.name) ($($AssetDetails.id)) is not monitored by a Segment Server (e.g uses Cloud Connector, Lightweight Agent). Only hosts monitored by a Segment Server can be pinned to a deployment cluster." - exit 1 + throw "Asset $($AssetDetails.name) ($($AssetDetails.id)) is not monitored by a Segment Server (e.g uses Cloud Connector, Lightweight Agent). Only hosts monitored by a Segment Server can be pinned to a deployment cluster." + } # 2nd: Check if asset is healthy, if not, error out. if ($AssetDetails.healthState.healthStatus -ne 1) { - Write-Error "Asset $($AssetDetails.name) ($($AssetDetails.id)) is not healthy! Please check the asset health in the portaland try again." - exit 1 - } - # 3rd: Check if asset is already pinned to a deployment cluster, if so, error out. - # deploymentsClusterSource = 5 --> NONE. - if ($AssetDetails.deploymentsClusterSource -ne 5 -and $AssetDetails.deploymentsClusterSource -ne 6) { - Write-Error "Asset $($AssetDetails.name) ($($AssetDetails.id)) is already pinned to Deployment Cluster ID: $($AssetDetails.deploymentsCluster.id) - Deployment Cluster Name: $($AssetDetails.deploymentsCluster.name) - Segment Server ID: $($AssetDetails.assignedDeployment.id) - Segment Server Name: $($AssetDetails.assignedDeployment.name)" - exit 1 + throw "Asset $($AssetDetails.name) ($($AssetDetails.id)) is not healthy! Please check the asset health in the portaland try again." + } - # 4th: If asset is not applicable to be pinned to a deployment cluster, error out. + # 3rd: If asset is not applicable to be pinned to a deployment cluster, error out. # deploymentsClusterSource = 6 --> NOT_APPLICABLE. - elseif ($AssetDetails.deploymentsClusterSource -eq 6) { - Write-Error "Asset $($AssetDetails.name) ($($AssetDetails.id)) applicable to be pinned to a deployment cluster!" - exit 1 + if ($AssetDetails.deploymentsClusterSource -eq 6) { + throw "Asset $($AssetDetails.name) ($($AssetDetails.id)) applicable to be pinned to a deployment cluster" + + } + # 4th: If workflow is looking unpin an asset, test that the asset IS ALREADY pinned to a deployment cluster. + if ($AssetMustBePinned) { + if (-not ($AssetIsPinnedDeploymentClusterSource -contains $AssetDetails.deploymentsClusterSource)) { + throw "Asset $($AssetDetails.name) ($($AssetDetails.id)) is not pinned to a deployment cluster! It must be pinned to a deployment cluster to be unpinned." + + } } + # 5th: Else, if workflow is looking to pin an asset, test that the asset IS NOT already pinned to a deployment cluster. + else { + if ($AssetIsPinnedDeploymentClusterSource -contains $AssetDetails.deploymentsClusterSource) { + throw "Asset $($AssetDetails.name) ($($AssetDetails.id)) is already pinned to Deployment Cluster ID: $($AssetDetails.deploymentsCluster.id) - Deployment Cluster Name: $($AssetDetails.deploymentsCluster.name) - Segment Server ID: $($AssetDetails.assignedDeployment.id) - Segment Server Name: $($AssetDetails.assignedDeployment.name)" + + } + } + Write-Host "Validated that asset $($AssetDetails.name) ($($AssetDetails.id)) can be pinned to deployment cluster: $($script:DeploymentClusterHashtable[$DeploymentClusterId].name)" } @@ -83,20 +213,20 @@ function Get-AssetDetails { try { $response = Invoke-ApiRequest -Method "GET" -ApiEndpoint "assets/$AssetId" if ($null -eq $response.entity) { - Write-Error "Asset details response is malformed and does not contain 'entity' property" - exit 1 + throw "Asset details response is malformed and does not contain 'entity' property" + } Write-Host "Found asset details for $($response.entity.name) - $AssetId" return $response.entity } catch { if ($null -ne $_.Exception.StatusCode -and ($_.Exception.StatusCode -eq 404)) { - Write-Error "Asset with ID $AssetId was not found!" - exit 1 + throw "Asset with ID $AssetId was not found" + } else { - Write-Error $_ - exit 1 + throw $_ + } } } @@ -106,30 +236,83 @@ function Set-AssetsToDeploymentCluster { [Parameter(Mandatory = $true)] [array]$AssetIdsArray, [Parameter(Mandatory = $true)] - [string]$DeploymentClusterId + [string]$DeploymentClusterId, + [Parameter(Mandatory = $false)] + [switch]$Unpin, + + [Parameter(Mandatory = $false)] + [switch]$DryRun ) $body = @{ assetIds = $AssetIdsArray - deploymentsClusterId = $DeploymentClusterId } - Write-Host "Pinning $($AssetIdsArray.Count) assets to deployment cluster: $($script:DeploymentClusterHashtable[$DeploymentClusterId].name)" - Invoke-ApiRequest -Method "PUT" -ApiEndpoint "/assets/actions/deployments-cluster" -Body $body + if (-not $Unpin) { + $body.deploymentsClusterId = $DeploymentClusterId + } + Write-Host "$($Unpin ? "Unpinning" : "Pinning") $($AssetIdsArray.Count) assets to deployment cluster: $($script:DeploymentClusterHashtable[$DeploymentClusterId].name)" + + if ($DryRun) { + Write-Host "[DRY RUN] Would $($Unpin ? "unpin" : "pin") $($AssetIdsArray.Count) assets to deployment cluster: $($script:DeploymentClusterHashtable[$DeploymentClusterId].name)" + } + else { + $response = Invoke-ApiRequest -Method "PUT" -ApiEndpoint "/assets/actions/deployments-cluster" -Body $body + Write-Host "Successfully $($Unpin ? "unpinned" : "pinned") $($AssetIdsArray.Count) assets to deployment cluster: $($script:DeploymentClusterHashtable[$DeploymentClusterId].name)" + } } <# This section of the script contains functions related to deployment cluster operations. #> +function Invoke-ValidateDeploymentClusterId { + param( + [Parameter(Mandatory = $true)] + [string]$DeploymentClusterId, + + [Parameter(Mandatory = $false)] + [switch]$SkipSegmentServerValidation + ) + Write-Host "Validating deployment cluster ID: $DeploymentClusterId" + if ($null -eq $script:DeploymentClusterHashtable) { + # If deployment cluster hashtable is not initialized, + # call function Get-DeploymentClusters to initialize it + Get-DeploymentClusters + } + if (-not $script:DeploymentClusterHashtable.ContainsKey($DeploymentClusterId)) { + throw "Deployment cluster ID $DeploymentClusterId not found" + } + + # Since the we validated the DeploymentClusterId exists, get it's object from the hashtable + $deploymentCluster = $script:DeploymentClusterHashtable[$DeploymentClusterId] + + # Get assigned deployments for the deployment cluster + if (-not $SkipSegmentServerValidation) { + #Validate that the deployment cluster has >= 1 active segment server + if ($deploymentCluster.assignedDeployments.Count -eq 0) { + throw "Deployment cluster `"$($deploymentCluster.name)`" ($DeploymentClusterId) has no segment servers assigned to it" + } + # Validate that at least one segment server is online (status=2 --> Online) + if ( + ($deploymentCluster.assignedDeployments | Where-Object {$_.status -eq "Online"}).Count -eq 0) { + throw "Deployment cluster `"$($deploymentCluster.name)`" ($DeploymentClusterId) has no online segment servers! Please check the segment server status in the portal and try again. If the segment server is online, please check the segment server health and try again." + } + } + else { + Write-Host "Skipping segment server validation" + } + Write-Host "Validated that deployment cluster `"$($deploymentCluster.name)`" ($DeploymentClusterId) exists in tenant" +} + function Get-DeploymentClusters { - Write-Host "Getting deployment clusters!" + Write-Host "Getting deployment clusters" $response = Invoke-ApiRequest -Method "GET" -ApiEndpoint "environments/cluster" if (-not $response.items) { - Write-Error "Deployment clusters response is malformed and does not contain 'items' property" - exit 1 + throw "Deployment clusters response is malformed and does not contain 'items' property" + } if ($response.items.Count -eq 0) { - Write-Error "No deployment clusters found!" - exit 1 + throw "No deployment clusters found" + } if ($response.items -isnot [System.Array]) { $DeploymentClusters = @($response.items) @@ -138,13 +321,8 @@ function Get-DeploymentClusters { $DeploymentClusters = $response.items } - # Test if DeploymentClusterFieldMappings.json file exists, if so, read it into a hashtable - if (Test-Path -Path "DeploymentClusterFieldMappings.json") { - $DeploymentClusters = Invoke-DecodeDeploymentClusterIDFields -DeploymentClusters $DeploymentClusters - } - else { - Write-Warning "DeploymentClusterFieldMappings.json file not found! Skipping deployment cluster ID field decoding!" - } + # Decode deployment cluster ID fields using script-wide hashtable + $DeploymentClusters = Invoke-DecodeDeploymentClusterIDFields -DeploymentClusters $DeploymentClusters # Create a script-wide hashtable of deployment clusters for easy access and validation New-DeploymentClusterHashtable -DeploymentClusters $DeploymentClusters @@ -160,7 +338,7 @@ function New-DeploymentClusterHashtable { foreach ($cluster in $DeploymentClusters){ $DeploymentClusterHashtable[$cluster.id] = $cluster } - Write-Host "Created script-wide hashtable of deployment clusters!" + Write-Host "Created script-wide hashtable of deployment clusters" } function Invoke-DecodeDeploymentClusterIDFields { @@ -169,30 +347,29 @@ function Invoke-DecodeDeploymentClusterIDFields { [System.Array]$DeploymentClusters ) Write-Host "Decoding deployment cluster ID fields! (e.g Updating strategy=2 --> strategy=Active/Active)" - $DeploymentClusterFieldMappings = Get-Content -Path "DeploymentClusterFieldMappings.json" | ConvertFrom-Json -AsHashtable -Depth 10 foreach ($cluster in $DeploymentClusters){ if ($cluster.strategy.GetType() -eq [System.Int64]) { - $cluster.strategy = $DeploymentClusterFieldMappings['strategy']['byId'][$cluster.strategy.ToString()] + $cluster.strategy = $script:DeploymentClusterFieldMappings['strategy']['byId'][$cluster.strategy.ToString()] } if ($cluster.assignedDeployments.Count -gt 0) { foreach ($deployment in $cluster.assignedDeployments){ if ($deployment.status.GetType() -eq [System.Int64]) { - $deployment.status = $DeploymentClusterFieldMappings['assignedDeployments.status']['byId'][$deployment.status.ToString()] + $deployment.status = $script:DeploymentClusterFieldMappings['assignedDeployments.status']['byId'][$deployment.status.ToString()] } if ($deployment.state.GetType() -eq [System.Int64]) { - $deployment.state = $DeploymentClusterFieldMappings['assignedDeployments.state']['byId'][$deployment.state.ToString()] + $deployment.state = $script:DeploymentClusterFieldMappings['assignedDeployments.state']['byId'][$deployment.state.ToString()] } if ($deployment.servicesInfo.Count -gt 0) { foreach ($service in $deployment.servicesInfo){ if ($service.serviceId.GetType() -eq [System.Int64]) { - $service.serviceId = $DeploymentClusterFieldMappings['assignedDeployments.servicesInfo.serviceId']['byId'][$service.serviceId.ToString()] + $service.serviceId = $script:DeploymentClusterFieldMappings['assignedDeployments.servicesInfo.serviceId']['byId'][$service.serviceId.ToString()] } if ($service.status.GetType() -eq [System.Int64]) { - $service.status = $DeploymentClusterFieldMappings['assignedDeployments.servicesInfo.status']['byId'][$service.status.ToString()] + $service.status = $script:DeploymentClusterFieldMappings['assignedDeployments.servicesInfo.status']['byId'][$service.status.ToString()] } if ($service.state.GetType() -eq [System.Int64]) { - $service.state = $DeploymentClusterFieldMappings['assignedDeployments.servicesInfo.state']['byId'][$service.state.ToString()] + $service.state = $script:DeploymentClusterFieldMappings['assignedDeployments.servicesInfo.state']['byId'][$service.state.ToString()] } } } @@ -208,7 +385,7 @@ function Write-DeploymentClusters { [object]$DeploymentClusters ) - Write-Host "Writing deployment clusters information to console!" + Write-Host "Writing deployment clusters information to console" foreach ($cluster in $DeploymentClusters){ Write-Host $("="*(($Host.UI.RawUI.WindowSize.Width)/2)) Write-Host "Deployment cluster: $($cluster.name)" @@ -217,7 +394,7 @@ function Write-DeploymentClusters { Write-Host "HA Strategy: $(if ($cluster.strategy -eq 2) { "Active/Active" } else { "Active/Passive" })" Write-Host "Segment server deployments assigned to this cluster:" if ($cluster.assignedDeployments.Count -eq 0) { - Write-Host "$(" "*2)No segment server deploments are assigned to this cluster!" + Write-Host "$(" "*2)No segment server deploments are assigned to this cluster" } else { Write-Host "$("~"*(($Host.UI.RawUI.WindowSize.Width)/4))" @@ -246,7 +423,7 @@ function Write-DeploymentClusters { } Write-Host $("="*(($Host.UI.RawUI.WindowSize.Width)/2)) } - Write-Host "Finished writing deployment clusters information to console!" + Write-Host "Finished writing deployment clusters information to console" } @@ -263,30 +440,34 @@ function Export-CsvTemplate { $template | Export-Csv -Path ".\pin-assets-to-clusters-template.csv" -NoTypeInformation Write-Host "CSV Template exported to .\pin-assets-to-clusters-template.csv" Write-Host "Please fill in AT LEAST the AssetId and DeploymentClusterId columsn, and then run the script again with the -CsvPath parameter to pin the assets to the clusters." - Write-Host "Example: .\Pin-AssetsToClusters.ps1 -CsvPath '.\pin-assets-to-clusters.csv' -ApiKey 'your-api-key'" - Exit 0 + Write-Host "Example: .\Pin-AssetsToClusters.ps1 -CsvPath '.\pin-assets-to-clusters-template.csv' -ApiKey 'your-api-key'" } function Get-CsvData { + param( + [Parameter(Mandatory = $true)] + [string]$CsvPath + ) # Check if CSV file exists if (-not (Test-Path -Path $CsvPath)) { - Write-Error "CSV file not found: $CsvPath" - exit 1 + throw "CSV file not found: $CsvPath" + } # Read CSV file into an array of PSCustomObjects try { $csvData = @(Import-Csv -Path $CsvPath) + Write-Host "Read $($csvData.Count) rows of CSV data" } catch { - Write-Error "Failed to read CSV file: $_" - exit 1 + throw "Failed to read CSV file: $_" + } # Validate that CSV has data if ($csvData.Count -eq 0) { - Write-Error "CSV file is empty or contains no data rows." - exit 1 + throw "CSV file is empty or contains no data rows." + } # Validate header row has required columns @@ -302,9 +483,9 @@ function Get-CsvData { } if ($missingColumns.Count -gt 0) { - Write-Error "CSV validation failed: The CSV file needs at least AssetId and DeploymentClusterId columns." - Write-Error "Actual columns found in CSV: $($actualColumns -join ', ')" - exit 1 + throw "CSV validation failed: The CSV file needs at least AssetId and DeploymentClusterId columns. Actual columns found in CSV: $($actualColumns -join ', ')" + + } # Note: Import-Csv automatically excludes the header row from the data array @@ -316,17 +497,18 @@ function Get-CsvData { # Check if AssetId is null if ($null -eq $row.AssetId) { - Write-Error "CSV validation failed: AssetId is null at row $csvRowNumber (index $i)" - exit 1 + throw "CSV validation failed: AssetId is null at row $csvRowNumber (index $i)" + } # Check if DeploymentClusterId is null if ($null -eq $row.DeploymentClusterId) { - Write-Error "CSV validation failed: DeploymentClusterId is null at row $csvRowNumber (index $i)" - exit 1 + throw "CSV validation failed: DeploymentClusterId is null at row $csvRowNumber (index $i)" + } } + Write-Host "Validated $($csvData.Count) rows of CSV data" # Return validated CSV data return $csvData } @@ -429,7 +611,7 @@ function Invoke-ApiRequest { ) try { - Write-Host "Sending $Method request to $script:ApiBaseUrl/$ApiEndpoint" + #Write-Host "Sending $Method request to $script:ApiBaseUrl/$ApiEndpoint" $requestParams = @{ Method = $Method Uri = "$script:ApiBaseUrl/$ApiEndpoint" @@ -450,15 +632,14 @@ function Invoke-ApiRequest { # Check for error status codes and handle accordingly Test-ApiResponseStatusCode -StatusCode $statusCode -Response $response - + return $response } catch { if ($null -ne $_.Exception.StatusCode -and ($_.Exception.StatusCode -eq 404)) { throw $_ } - Write-Error "API request to $($requestParams['Uri']) failed due to error:`n$_" - exit 1 + throw "API request to $($requestParams['Uri']) failed due to error:`n$_" } } @@ -468,16 +649,73 @@ workflow to execute based on the parameter set matched. #> switch ($PSCmdlet.ParameterSetName) { "ByAssetId" { - Write-Host "Starting workflow to pin asset $AssetId to deployment cluster $DeploymentClusterId" + Write-Host "$($DryRun ? "[DRY RUN] " : '') Starting workflow to $($Unpin ? "unpin" : "pin") asset $AssetId to deployment cluster $DeploymentClusterId" Initialize-ApiContext - $AssetDetails = Get-AssetDetails -AssetId $AssetId - Test-AssetCanBePinned -AssetDetails $AssetDetails - Set-AssetsToDeploymentCluster -AssetIdsArray @($AssetId) -DeploymentClusterId $DeploymentClusterId - "" + # Validate deployment cluster ID + Invoke-ValidateDeploymentClusterId -DeploymentClusterId $DeploymentClusterId -SkipSegmentServerValidation:$SkipSegmentServerValidation + # Test if asset can be pinned/unpinned to deployment cluster + Test-AssetCanBePinned -AssetId $AssetId -AssetMustBePinned:$Unpin + Write-Host "Unpinning asset $AssetId from deployment cluster $DeploymentClusterId" + # Either pin or unpin asset to deployment cluster, dependent on the -Unpin switch parameter + Set-AssetsToDeploymentCluster -AssetIdsArray @($AssetId) -DeploymentClusterId $DeploymentClusterId -Unpin:$Unpin -DryRun:$DryRun + Write-Host "$($DryRun ? "[DRY RUN] " : '') Finished workflow to $($Unpin ? "unpin" : "pin") asset $AssetId to deployment cluster $DeploymentClusterId" + } "ByCsvPath" { + Write-Host "$($DryRun ? "[DRY RUN] " : '') Starting workflow to $($Unpin ? "unpin" : "pin") assets from CSV file $CsvPath" Initialize-ApiContext - $csvData = Get-CsvData + # Read CSV data into an array of PSCustomObjects + $csvData = Get-CsvData -CsvPath $CsvPath + # Get unique deployment cluster IDs from the CSV data + $UniqueClusterIds = @($csvData.DeploymentClusterId | Select-Object -Unique) + # Validate that all deployment cluster IDs in the CSV are valid and exist in tenant + foreach ($clusterId in $UniqueClusterIds) { + Invoke-ValidateDeploymentClusterId -DeploymentClusterId $clusterId -SkipSegmentServerValidation:$SkipSegmentServerValidation + } + + # Test current state of assets to validate each can be pinned/unpinned to deployment clusters + foreach ($row in $csvData) { + Test-AssetCanBePinned -AssetId $row.AssetId -AssetMustBePinned:$Unpin + } + Write-Host "Validated that all assets can be $($Unpin ? "unpinned" : "pinned") to deployment clusters" + + # Use UniqueClusterIds to get unique assets that are to be pinned/unpinned to that cluster + # This is an cluster --> assets map + $AssetClusterMappingHashtable = @{} + foreach ($clusterId in $UniqueClusterIds) { + $AssetClusterMappingHashtable[$clusterId] = @( $csvData | Where-Object { $_.DeploymentClusterId -eq $clusterId } ) + } + Write-Host "Created asset cluster mapping hashtable" + foreach ($clusterId in $AssetClusterMappingHashtable.Keys) { + # Extract AssetIds from CSV objects + $assetIds = $AssetClusterMappingHashtable[$clusterId] | ForEach-Object { $_.AssetId } + $totalAssets = $assetIds.Count + + Write-Host "$($Unpin ? "Unpinning" : "Pinning") $totalAssets assets to deployment cluster $($script:DeploymentClusterHashtable[$clusterId].name)" + + if ($totalAssets -gt 50) { + # Process in batches of 50 + $batchSize = 50 + $batchNumber = 1 + $totalBatches = [math]::Ceiling($totalAssets / $batchSize) + + for ($i = 0; $i -lt $totalAssets; $i += $batchSize) { + $batch = $assetIds[$i..([math]::Min($i + $batchSize - 1, $totalAssets - 1))] + Write-Host "Processing batch $batchNumber of $totalBatches ($($batch.Count) assets)..." + Set-AssetsToDeploymentCluster -AssetIdsArray $batch -DeploymentClusterId $clusterId -Unpin:$Unpin -DryRun:$DryRun + if (-not $DryRun) { + Write-Host "Successfully $($Unpin ? "unpinned" : "pinned") $($batch.Count) assets to deployment cluster $($script:DeploymentClusterHashtable[$clusterId].name)" + } + $batchNumber++ + } + + } + else { + # Process all assets at once if 50 or fewer + Set-AssetsToDeploymentCluster -AssetIdsArray $assetIds -DeploymentClusterId $clusterId -Unpin:$Unpin -DryRun:$DryRun + } + } + Write-Host "$($DryRun ? "[DRY RUN] " : '') Finished workflow to $($Unpin ? "unpin" : "pin") assets from CSV file $CsvPath" } "ListDeploymentClusters" { Initialize-ApiContext diff --git a/Segment/Segment/Asset Management/Pin Assets To Clusters/README.md b/Segment/Segment/Asset Management/Pin Assets To Clusters/README.md new file mode 100644 index 0000000..6842077 --- /dev/null +++ b/Segment/Segment/Asset Management/Pin Assets To Clusters/README.md @@ -0,0 +1,332 @@ +# Pin Assets To Clusters + +A PowerShell script for pinning (assigning) or unpinning (unassigning) assets to a particular deployment cluster in Zero Networks (Network/Identity/RPC Segment). This script allows you to pin/unpin individual assets, or in bulk via CSV. + +## Requirements + +- **PowerShell 7.0 or higher** +- **Zero Networks API Key** with appropriate permissions + +## Features + +- Pin or unpin individual assets to deployment clusters +- Bulk operations via CSV file +- Automatic batching for large asset lists (50 assets per batch) +- Dry run mode to preview changes without applying them (`-DryRun`) +- Comprehensive validation before making changes +- List all deployment clusters with detailed information (`-ListDeploymentClusters`) +- Export CSV template for bulk operations (`-ExportCsvTemplate`) + +## Prerequisites for pinning an asset to a cluster +You can only pin assets that meet the following criterion: +- **Must be monitored by a segment server** - You cannot pin assets monitored by Cloud Connector, Segment Connector, etc. +- **Must have a health status of *Healthy*** - The asset cannot have any health issues listed. +- **Target cluster must have at least one online and active segment server.** *You can skip this check by adding the `-SkipSegmentServerValidation` parameter at run-time*. +- **Obviously, an asset cannot already be pinned** (The script checks for this) + + +## Parameter Sets + +The script supports four parameter sets: + +### 1. ByAssetId (Default) +Pin or unpin a single asset to a deployment cluster. + +**Required Parameters:** +- `-ApiKey` - Your Zero Networks API key +- `-AssetId` - The asset ID to pin/unpin +- `-DeploymentClusterId` - The deployment cluster ID + +**Optional Parameters:** +- `-PortalUrl` - Portal URL (default: `https://portal.zeronetworks.com`) +- `-Unpin` - Switch to unpin instead of pin +- `-SkipSegmentServerValidation` - Skip validation that segment servers are online +- `-DryRun` - Preview changes without applying them + +### 2. ByCsvPath +Pin or unpin multiple assets from a CSV file. + +**Required Parameters:** +- `-ApiKey` - Your Zero Networks API key +- `-CsvPath` - Path to the CSV file + +**Optional Parameters:** +- `-PortalUrl` - Portal URL (default: `https://portal.zeronetworks.com`) +- `-Unpin` - Switch to unpin instead of pin +- `-SkipSegmentServerValidation` - Skip validation that segment servers are online +- `-DryRun` - Preview changes without applying them + +### 3. ListDeploymentClusters +List all deployment clusters with detailed information. + +**Required Parameters:** +- `-ApiKey` - Your Zero Networks API key +- `-ListDeploymentClusters` - Switch to enable listing mode + +**Optional Parameters:** +- `-PortalUrl` - Portal URL (default: `https://portal.zeronetworks.com`) + +### 4. ExportCsvTemplate +Export a CSV template file for bulk operations. + +**Required Parameters:** +- `-ExportCsvTemplate` - Switch to enable template export + +## Quick Start +### Single asset pinning +#### 1. Find the asset ID +- Within the Zero Networks portal, go to **Entities -> Assets -> Monitored** and add the necessary filters to find the asset in question (e.g filter by Name -> MY-PC-1). +- Enable the **Asset ID** column in the table of assets +- Copy the **Asset ID** to a text document to be referenced later + +#### 2. Run script with -ListDeploymentClusters parameter to get Cluster ID +Run the script with the `-ListDeploymentClusters` parameter. This will output information about all clusters in the tenant to your console. + +*The data below is actual output, but specific identifying information has been changed for secrecy.* +```powershell +./Pin-AssetsToClusters.ps1' -ApiKey -PortalUrl https://.zeronetworks.com -ListDeploymentClusters +... +Getting deployment clusters +Decoding deployment cluster ID fields! (e.g Updating strategy=2 --> strategy=Active/Active) +Created script-wide hashtable of deployment clusters +Writing deployment clusters information to console +====================================================================================================================== +Deployment cluster: ZN deployments cluster +Cluster ID: C:d:00ab123d +Number of assets in cluster: 10 +HA Strategy: Active/Passive +Segment server deployments assigned to this cluster: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Name: MY-SEGMENT-SRV001 + Deployment ID: 47792f8f-1213-1234-abcd-4fb47f2794d3 + Server Asset ID: a:a:JabcdEjT + Status: Online + State: Primary + Num Assets Associated: 10 + Internal IP Address: 10.2.3.4 + External IP Address: 1.20.21.22 + Segment Server Version: 25.10.3.4 + Is Preferred Deployment: Yes + Deployment Services: + -------------------------------- + Service ID: ad + Service Status: Online + Service State: Primary + -------------------------------- + -------------------------------- + Service ID: winrm + Service Status: Online + Service State: Primary + -------------------------------- + -------------------------------- + Service ID: ansible-manager + Service Status: Online + Service State: Primary + -------------------------------- + ----------------------------------------------------------- +====================================================================================================================== +====================================================================================================================== +Deployment cluster: EU Cluster +Cluster ID: C:d:8PghlCty +Number of assets in cluster: 0 +HA Strategy: Active/Passive +Segment server deployments assigned to this cluster: + No segment server deploments are assigned to this cluster +====================================================================================================================== +Finished writing deployment clusters information to console +``` + +#### 3. Extract cluster ID(s) from output +Analyze the output from the script ran with `-ListDeploymentClusters`. Make note of each **Cluster ID(s)** you wish to pin the asset(s) to. E.g ```C:d:8PghlCty``` + +#### 4. Pin the asset to the cluster +Run the script in single asset mode, specifying a particular asset and cluster ID. This will pin this asset to the specified cluster. + +```powershell +.\Pin-AssetsToClusters.ps1 ` + -ApiKey "your-api-key" ` + -AssetId "a:a:qvI6tVtn" ` + -DeploymentClusterId "C:d:00fd409f" ` + -PortalUrl "https://your-portal.zeronetworks.com" +``` + +### Bulk Operations +#### 1. Export applicable assets to CSV within the portal +- From within the Zero Networks portal, go to *Entities -> Assets -> Monitored* and add the filter **Monitored By --> Segment Server** and **Health Status --> Healthy** (Prerequisites). +- Filter the list additionally until it only displays assets you wish to pin/unpin. +- Export the list to a CSV file + +#### 2. Run the script to generate a CSV template +To facilitate ease of use, the script, when ran with the `-ExportCsvTemplate` parameter, will export a CSV template at `./pin-assets-to-clusters-template.csv` + +#### 3. List deployment clusters +Follow the steps [2. Run script with -ListDeploymentClusters parameter to get Cluster ID](#2-run-script-with--listdeploymentclusters-parameter-to-get-cluster-id) and [3. Extract cluster ID(s) from output](#3-extract-cluster-ids-from-output) from the [Single asset pinning](#single-asset-pinning) section above to obtain a list of relevant Cluster IDs. + +#### 4. Populate CSV template +Copy the asset IDs (and asset names, if desired) into the CSV template previously generated. Copy and paste the cluster ID (for which you wish to pin that asset to)in the *DeploymentClusterId* column of the CSV for each asset. + +Your CSV should look similar to: +```csv +AssetName,AssetId,DeploymentClusterId +Server-01,a:a:qvI6tVtn,C:d:00fd409f +Server-02,a:a:abc123,C:d:00fd409f +Server-03,a:a:def456,C:d:00fd409g +``` + +To review the required columns in the CSV, please read [CSV file format](#csv-file-format). + +#### 5. Run script against the CSV +Finally, run the script, passing it the path to your CSV. + +```powershell +.\Pin-AssetsToClusters.ps1 ` + -ApiKey "your-api-key" ` + -CsvPath ".\pin-assets-to-clusters-template.csv" ` + -PortalUrl "https://your-portal.zeronetworks.com" +``` + +## Usage Examples + +### Pin a Single Asset + +```powershell +.\Pin-AssetsToClusters.ps1 ` + -ApiKey "your-api-key" ` + -AssetId "a:a:qvI6tVtn" ` + -DeploymentClusterId "C:d:00fd409f" ` + -PortalUrl "https://your-portal.zeronetworks.com" +``` + +### Unpin a Single Asset + +```powershell +.\Pin-AssetsToClusters.ps1 ` + -ApiKey "your-api-key" ` + -AssetId "a:a:qvI6tVtn" ` + -DeploymentClusterId "C:d:00fd409f" ` + -Unpin +``` + +### Pin Assets from CSV File + +```powershell +.\Pin-AssetsToClusters.ps1 ` + -ApiKey "your-api-key" ` + -CsvPath ".\pin-assets-to-clusters-template.csv" ` + -PortalUrl "https://your-portal.zeronetworks.com" +``` + +### Unpin Assets from CSV File + +```powershell +.\Pin-AssetsToClusters.ps1 ` + -ApiKey "your-api-key" ` + -CsvPath ".\pin-assets-to-clusters-template.csv" ` + -Unpin +``` + +### Dry Run (Preview Changes) + +```powershell +.\Pin-AssetsToClusters.ps1 ` + -ApiKey "your-api-key" ` + -CsvPath ".\pin-assets-to-clusters-template.csv" ` + -DryRun +``` + +### List All Deployment Clusters + +```powershell +.\Pin-AssetsToClusters.ps1 ` + -ApiKey "your-api-key" ` + -ListDeploymentClusters +``` + +### Export CSV Template + +```powershell +.\Pin-AssetsToClusters.ps1 -ExportCsvTemplate +``` + +## CSV File Format + +The CSV file must contain at least the following columns: + +- **AssetId** (required) - The asset ID to pin/unpin +- **DeploymentClusterId** (required) - The deployment cluster ID +- **AssetName** (optional) - Asset name for reference + +Example CSV: + +```csv +AssetName,AssetId,DeploymentClusterId +Server-01,a:a:qvI6tVtn,C:d:00fd409f +Server-02,a:a:abc123,C:d:00fd409f +Server-03,a:a:def456,C:d:00fd409g +``` + +## Validation + +The script performs comprehensive validation before making any changes: + +### Asset Validation +- Asset must be monitored by a Segment Server (not Cloud Connector or Lightweight Agent) +- Asset must be healthy +- Asset must be applicable to be pinned to a deployment cluster (An asset's deploymentSource attribute cannot be set to "Not Applicable") +- For pinning: Asset must not already be pinned to a deployment cluster +- For unpinning: Asset must already be pinned to a deployment cluster + +### Deployment Cluster Validation +- Deployment cluster must exist in the tenant +- Deployment cluster must have at least one segment server assigned (unless `-SkipSegmentServerValidation` is used) +- At least one segment server must be online (unless `-SkipSegmentServerValidation` is used) + + +## Dry Run Mode + +Use the `-DryRun` switch to preview what changes would be made without actually applying them. In dry run mode: + +- All validations are still performed +- The script shows what would be done +- The request body that would be sent is displayed +- No API calls are made to modify asset assignments + +## Error Handling + +The script includes comprehensive error handling: + +- **404 errors**: Asset or deployment cluster not found +- **400/401/403/405 errors**: Bad request, unauthorized, forbidden, or method not allowed +- **500/501/503 errors**: Server errors with detailed messages +- **Validation errors**: Clear messages when assets or clusters don't meet requirements + +All errors include the status code, reason phrase, and response body when available. + +## Output + +The script provides detailed console output including: + +- Progress messages for each operation +- Validation results +- Batch processing information (for large operations) +- Success confirmations +- Error messages with context + +## Notes + +- The script requires PowerShell 7.0 or higher +- API keys should be kept secure and not committed to version control +- Large operations may take time depending on the number of assets and network conditions +- The script uses `$ErrorActionPreference = "Stop"` to ensure errors are handled properly + +## Troubleshooting + +### "Asset is not monitored by a Segment Server" +The asset must be using a Segment Server, not Cloud Connector or Lightweight Agent. + +### "Deployment cluster has no online segment servers" +Ensure at least one segment server in the deployment cluster is online. Use `-SkipSegmentServerValidation` to bypass this check if needed. + +### "Asset is already pinned to a deployment cluster" +The asset is already pinned to a cluster. Use `-Unpin` first if you want to change the assignment. +