# Get-CMDBNodeList is designed to scan a Polaris/RSC environment and its attached CDM clusters, and pull metadata on each of the nodes # on each cluster in order to build out a "map" of CDM nodes that can be imported into CMDB for use with SNOW. # Author: Tim Robbins with Rubrik PSO # Published on 01/09/2023 #------------------------------------------------------------------------------------------------------------------------------------- # Change log #------------------------------------------------------------------------------------------------------------------------------------- # 1/10/2023 - JW - Added the Management hostname and iDRAC/IPMI hostname to the report. # 1/12/2023 - JW - If the IP address is reported as 0.0.0.0 then change it to UNKNOWN. #------------------------------------------------------------------------------------------------------------------------------------- # Usage -- Change 3 lines for the local environment. # Line 16ish -- Change the $Logfile variable with the correct path. # Line 205ish -- Change the $serviceAccountJSON variable with the correct path and filename for the service account. # Lines 227 and 228ish -- Change the path for the Add-Content lines. #------------------------------------------------------------------------------------------------------------------------------------- $Logfile = "C:\temp\Get-CMDBNodeList.log" function WriteLog { # Function to write to a logfile with a log timestamp on the entry Param ( [Parameter(Mandatory)] [string]$LogString, [Parameter(Mandatory=$false)] [string]$AltLogFile ) if(!$AltLogFile){ $Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss") $LogMessage = "$Stamp $LogString" Add-content $LogFile -value $LogMessage } else { # Sending to an alternate location instead of the default log file $Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss") $LogMessage = "$Stamp $LogString" Add-content $AltLogFile -value $LogMessage } } function connect-polaris { # Function that uses the Polaris/RSC Service Account JSON and opens a new session, and returns the session temp token [CmdletBinding()] param ( # Service account JSON file [Parameter(Mandatory)] [string] $serviceAccountJson ) begin { # Parse the JSON and build the connection string $serviceAccountObj = $serviceAccountJson | ConvertFrom-Json $connectionData = [ordered]@{ 'client_id' = $serviceAccountObj.client_id 'client_secret' = $serviceAccountObj.client_secret } | ConvertTo-Json } process { try{ $polaris = Invoke-RestMethod -Method Post -uri $serviceAccountObj.access_token_uri -ContentType application/json -body $connectionData } catch [System.Management.Automation.ParameterBindingException]{ Write-Error("The provided JSON has null or empty fields, try the command again with the correct file or redownload the service account JSON from Polaris") } } end { if($polaris.access_token){ Write-Output("$($polaris.access_token)") } else { Write-Error("Unable to connect") } } } function disconnect-polaris { # Closes the session with the session token passed here [CmdletBinding()] param ( # session object [Parameter(Mandatory)] [string] $session ) begin { } process { try{ $closeStatus = $(Invoke-WebRequest -Method Delete -Headers $auth_Header -ContentType "application/json; charset=utf-8" -Uri $logoutUrl).StatusCode } catch [System.Management.Automation.ParameterBindingException]{ Write-Error("Failed to logout. Error $($_)") } } end { if({$closeStatus -eq 204}){ Write-Output("Successfully logged out") } else { Write-Error("Error $($_)") } } } function get-nodes{ # Leverages the DataSitesMapQuery and ClusterNodesList GraphQL queries to pull specific node metadata from all clusters in RSC instance. process { try { $query = ' { "query": "query DataSitesMapQuery {\n clusterConnection(filter: {type: [OnPrem, Cloud, Robo]}) {\n nodes {\n id\n name\n}\n __typename\n }\n}\n"} ' $clusters = Invoke-RestMethod -Method Post -Headers $auth_Header -ContentType "application/json; charset=utf-8" -Uri $Url -Body $query Write-Host($clusters) $AllCDMNodes = @() ForEach ($cluster in $clusters.data.clusterConnection.nodes) { $query2 = '{"query":"query ClusterNodesList {\n cluster(clusterUuid: \"'+$($cluster.id)+'\") {\n id\n status\n clusterNodeConnection {\n edges {\n node {\n id\n hostname\n position\n brikId\n status\n ipAddress\n }\n }\n }\n systemStatusAffectedNodes {\n id\n }\n cdmClusterNodeDetails {\n nodeId\n dataIpAddress\n ipmiIpAddress\n }\n }\n}","variables":{}}' $clusterNodes = Invoke-RestMethod -Method Post -Headers $auth_Header -ContentType "application/json; charset=utf-8" -Uri $Url -Body $query2 #WriteLog $($clusterNodes | ConvertTo-Json -depth 100) # Iterate through the nodes JSON from the CNL query above, ForEach ($node in $clusterNodes.data.cluster.clusterNodeConnection.edges.node) { $tempCND = $($clusterNodes.data.cluster.cdmClusterNodeDetails.Where({$_.nodeId -eq $($node.id)})) if($clusterNodes.data.cluster.cdmClusterNodeDetails.count -eq 0){ WriteLog "Cluster $($cluster.id) is being stupid" } # Check if brikId shows a virtual platform, and set value accordingly if($node.brikId -eq "RUBRIK"){ if($tempCND.nodeId.Substring(0,4) -eq "VRVW"){ $chassis = "VIRTUAL" $tempCND.ipmiIpAddress = "UNKNOWN" $nodeImpiHostname = "null" $nodeMgmtHostname = "null" } elseif($tempCND.nodeId.Substring(0,4) -eq "VRAZ"){ $chassis = "AZURE" $tempCND.ipmiIpAddress = "UNKNOWN" } else{ $chassis = "UNKNOWN" } } else{ $chassis = $node.brikId if($tempCND.ipmiIpAddress.Substring(0,7) -eq "0.0.0.0"){ $tempCND.ipmiIpAddress = "UNKNOWN" $nodeImpiHostname = "null" } else{ $nodeImpiHostname = Resolve-DnsName $($tempCND.ipmiIpAddress) -ErrorAction Ignore | select namehost #WriteLog "IMPI IP: $($tempCND.ipmiIpAddress)" #WriteLog "IMPI Hostname: $($nodeImpiHostname.NameHost)" } $nodeMgmtHostname = Resolve-DnsName $node.ipAddress -ErrorAction Ignore | select namehost #WriteLog "Mgmt IP: $($node.ipAddress)" #WriteLog "Mgmt Hostname: $($nodeMgmtHostname.NameHost)" } # Check if the node ID starts with a Dell identifier, then pulls the Dell service tag from the node ID $serviceTag="N/A" if($tempCND.nodeId.Substring(0,7) -eq "RDL6420"){ $serviceTag = $($tempCND.nodeId.SubString(7).Substring(0,7)) $position = $($tempCND.nodeId.SubString($tempCND.nodeId.Length-2)) } else { $position="$($node.position)" } #WriteLog "IMPI IP #2 $($tempCND.ipmiIpAddress)" $AllCDMNodes += [pscustomobject]@{ clusterId = $cluster.id clusterName = $cluster.name nodeId = $node.id nodeServiceTag = $serviceTag # nodeDataIp = $($tempCND.dataIpAddress) nodeIpmiIp = $($tempCND.ipmiIpAddress) nodeIpmiHostname = $nodeImpiHostname.NameHost nodeChassisId = $chassis nodePosition = $position nodeMgmtIp = $node.ipAddress nodeMgmtHostname = $nodeMgmtHostname.NameHost # nodeHostname = $node.hostname #$nodeMgmtHostname = Resolve-DnsName $node.ipAddress | select namehost #WriteLog "Hostname: $($nodeMgmtHostname.NameHost)" } } } Write-Output $AllCDMNodes } Catch{ Write-Error("Error $($_)") } } End { Write-Output $AllCDMNodes } } # ServiceAccount JSON path, and some parsed variables for endpoints on the instance noted in JSON $serviceAccountJSON = Get-Content -Path "C:\temp\storage_test.json" | ConvertFrom-Json $instance=$($serviceAccountJSON.access_token_uri.split("/")[2]) $Url = "https://$instance/api/graphql" $logoutUrl = "https://$instance/api/session" # Open Polaris session and return token $polSession=connect-polaris($serviceAccountJSON | ConvertTo-Json) WriteLog("Connected to instance $instance with session token $polSession") # Auth header using token from opened session $auth_Header = @{ "Authorization" = "Bearer $polSession"; } # Use the auth header above for calls in the get-nodes function to build the custom JSON array $nodeJSON=get-nodes # TO-DO: figure out why this is breaking. #Write-Host "nodeJSON variable type is: $($nodeJSON.GetType())" #WriteLog($nodeJSON,"CMDB.out") # Workaround for above issue writing via WriteLog function Add-Content "C:\temp\CMDB.out" -value $($nodeJSON) Add-Content "C:\temp\CMDB.csv" -value $($nodeJSON | ConvertTo-Csv) WriteLog("Node JSON written to CMDB.out") # Cleanup session WriteLog("Disconnect status: $(disconnect-polaris($polSession))")