<# .DESCRIPTION This script will generate the migration-night etherpad. It will poll New Relic and Core to gather all the details we were manually gathering previously. It will generate a text file that can be pasted right into etherpad. .EXAMPLE & '.\Etherpad Generator.ps1' -MG IAD2-MG-07 Generates the etherpad for move group "IAD2-MG-07" using the results of the 2-week audit milestone. .EXAMPLE & '.\Etherpad Generator.ps1' -MG IAD2-MG-07 -Only4 Generates the etherpad for move group "IAD2-MG-07" using the results of the 4-week audit milestone. #> Param ( [Parameter(Mandatory=$false,Position=0)][string]$Version = "4.9", #Script version [Parameter(Mandatory=$True,Position=0)][string]$MG, #Move group [Parameter(Mandatory=$false,Position=0)][string]$Device, #Check details of a specific device [switch]$Only4 #Poll NR for 4-week results rather than 2-week ) ########################################################################################################### Function Write-EPHeader ($OutFile) { <# *********************************************************************************************************** This function writes the pre-migration header for the Etherpad file *********************************************************************************************************** #> $Outline = "Premigration" set-content $OutFile $OutLine $Outline = "Custom Instructions" add-content $OutFile $OutLine $Outline = "***************************************************************************************************" add-content $OutFile $OutLine } ########################################################################################################### Function Write-EPPostHeader ($OutFile) { <# *********************************************************************************************************** This function writes the post-migration header for the Etherpad file *********************************************************************************************************** #> $Outline = "Post Migration" add-content $OutFile $OutLine $Outline = "Custom Instructions" add-content $OutFile $OutLine $Outline = "***************************************************************************************************" add-content $OutFile $OutLine } ########################################################################################################### Function Write-EPAccountHeader ($OutFile,$PassedAccount) { <# *********************************************************************************************************** This function writes the individual account header for the Etherpad file *********************************************************************************************************** #> Write-Verbose -Message "`t`t`tWriting account header for account $($PassedAccount.Name)." $Outline = $PassedAccount.name+" - "+$PassedAccount.number add-content $OutFile $OutLine $Outline = "https://core.rackspace.com/py/core/#/account/$($PassedAccount.number)" add-content $OutFile $OutLine $Outline = "" add-content $OutFile $OutLine } ########################################################################################################### Function Write-EPAccountSpacer ($OutFile) { <# *********************************************************************************************************** This function writes two blank likes in between each account to allow for spacing out accounts. *********************************************************************************************************** #> $Outline = "" add-content $OutFile $OutLine add-content $OutFile $OutLine } ########################################################################################################### Function Write-EPCluster ($OutFile,$NRCluster,$NSIResults,$CoreClusters,$AccountName) { <# *********************************************************************************************************** This function writes all the appropriate data for a cluster. *********************************************************************************************************** #> #Writing header add-content $OutFile "Cluster Nodes as Defined by Core:" #Writing the cluster nodes as we've uncovered within Core along with the link to Core foreach ($Node in $NRCluster.CoreNodes) { add-content $OutFile $Node $OutLine = "https://core.rackspace.com/?computer_number=$Node" add-content $OutFile $OutLine } ########################################################################################## #Filtering out pre-2008 R2 clusters as they're flagged differently within the Status field if ($NRCluster.Status -notlike "*WARNING*") { ########################################################################################## add-content $OutFile "" add-content $OutFile "Cluster Nodes as Detected:" for ($i = 0; $i-lt $NRCluster.Nodes.count;$i++) { $OutLine = $NRCluster.Nodes[$i]+"`t"+$NRCluster.NodeStatuses[$i] add-content $OutFile $OutLine } add-content $OutFile "" ########################################################################################## add-content $OutFile "Cluster Status:" add-content $OutFile $NRCluster.Status add-content $OutFile "" add-content $OutFile "Core-Expected Cluster Instances:" ########################################################################################## #Iterating through the cluster instances we've identified in Core ForEach ($CoreCluster in $CoreClusters) { $ClusterMatch = $False #Iterating through the individual nodes within the cluster instances we've identified in Core foreach ($CoreNode in $CoreCluster.Nodes) { #iterating through the cluster instances we've identified within New Relic and attempting to match with our NR record and flag if we find a match foreach ($NRNode in $NRCluster.CoreNodes) { if ($CoreNode -eq $NRNode) { $ClusterMatch = $True } } } ########################################################################################## #If we get a match we'll proceed to output the appropriate data if ($ClusterMatch) { #Iterate through the instances as identified within Core. This property should just be a list of sanitized device numbers. foreach ($CoreInstance in $CoreCluster.Instances) { $RegistryCommand = "" #Iterate through our NSI results to find migration specifics about the instance ForEach ($Device in $NSIResults) { if ($Device._id -eq $CoreInstance -and $Device.device.hostname -notlike "*dtc*" -and $Device.device.hostname -notlike "*DTC*" -and $Device.device.hostname -notlike "*ntclus*" -and $Device.device.hostname -notlike "*NTClus*") { #Removing the phony FQDN and writing the hostname $Outline = [string]$CoreInstance+" - "+(ParseLine $Device.device.hostname 0 "." " ")[0] add-content $OutFile $OutLine $OutLine = "https://core.rackspace.com/?computer_number=$CoreInstance" add-content $OutFile $OutLine add-content $OutFile "" ###################################################################################### #Make sure we have a destination SNet IP address if ($Device.network.address.service_net_ips.migration.ip_address -ne $Null) { $RegistryCommand = "REG ADD `"HKLM\SOFTWARE\CommVault Systems\Galaxy\Instance001\Machines\"+[string]$($Device.account.number)+"-"+[string]$($Device._id)+"`" /v `"sHOSTNAME`" /t REG_SZ /d `""+$($Device.network.address.service_net_ips.migration.ip_address)+"`" /f" } ###################################################################################### #The SNet nmight be in a couple of places elseif ($Device.network.address.mbu_ips.migration.ip_address -ne $Null) { $RegistryCommand = "REG ADD `"HKLM\SOFTWARE\CommVault Systems\Galaxy\Instance001\Machines\"+[string]$($Device.number)+"-"+[string]$($Device._id)+"`" /v `"sHOSTNAME`" /t REG_SZ /d `""+$($Device.network.address.service_net_ips.migration.ip_address)+"`" /f" } ###################################################################################### #If the SNet IP is in neither of the two known places then notify the user else { $RegistryCommand = "Device does hot have destination ServiceNet IPs" } #Writing the registry output for the cluster add-content $OutFile $RegistryCommand add-content $OutFile "" } } } } } ###################################################################################### #Output of offline resources #For some reason when there are no offline resources there's an artifact where the count is 1. I'm filtering that out here. Our script tells us if there are offline resources or not. if ($NRCluster.OfflineResNames.count -gt 0 -and $NRCluster.Status -notlike "*No offline*") { add-content $OutFile "Offline Resources:" for ($i = 0; $i -lt $NRCluster.OfflineResNames.count; $i++) { $OutLine = $NRCluster.OfflineResNames[$i]+"`t"+$NRCluster.OfflineResStates[$i]+"`t"+$NRCluster.OfflineResTypes[$i] add-content $OutFile $OutLine } add-content $OutFile "" } else { add-content $OutFile "All resources online." add-content $OutFile "" } add-content $OutFile "Cluster IPs" ###################################################################################### #Output of cluster IPs for ($i = 0; $i -lt $NRCluster.IPs.count; $i++) { $Outline = $NRCluster.IPs[$i]+"`t"+$NRCluster.IPResNames[$i]+"`t"+$NRCluster.IPNetNames[$i]#+"`t"+$NRCluster.IPOwners[$i] add-content $OutFile $OutLine } add-content $OutFile "" } else { #Pre-2008 cluster stuff $OutLine = "Cluster is pre 2008 R2" add-content $OutFile $OutLine } ###################################################################################### #Writing the Core link for the device add-content $OutFile "" add-content $OutFile "" } ########################################################################################################### Function Write-EPException ($OutFile,$PulledDevicePassed,$Exception) { <# *********************************************************************************************************** This function takes the current pulled device writes the issue we're flagging to the Etherpad file. *********************************************************************************************************** #> Write-Verbose -Message "`t`t`tWriting event $Exception for device $($PulledDevicePassed._id)." ###################################################################################### #As we're getting the hostname in slightly different ways we have to sanitize it. The first case is when we get it from a defined issue if ($PulledDevicePassed.Hostname -eq $Null) { $Hostname = (ParseLine $PulledDevicePassed.device.hostname 0 "." " ")[0] } else { #If the issues is not on our defined list we're submitting the host name directly $Hostname = $PulledDevicePassed.Hostname } ###################################################################################### #adding the information to the file add-content $OutFile $Hostname add-content $OutFile $Exception ###################################################################################### #Writing the Core link $OutLine = "https://core.rackspace.com/?computer_number=$($PulledDevicePassed._id)" add-content $OutFile $OutLine $OutLine = "" add-content $OutFile $OutLine } ########################################################################################################### Function Process-NRClusters ($ClusterEvents) { <# *********************************************************************************************************** This function takes the New Relic poll and assembles clusters based on an analysis performed. *********************************************************************************************************** #> $ReturnClusters = @() #our array to hold cluster node/instance collections $CurrentResult = 0 #progress variable $ClusterMatch = $True #Check variable for whether or not this is a new cluster node/instance set $ThisCluster = $True #Check variable for whether or not this is the cluster into which we need to insert a value foreach ($Event in $ClusterEvents) { ########################################################################## #Writing progress $CurrentEvent++ $EventPercentageComplete = ($CurrentEvent/$ClusterEvents.count)*100 $EventPercentageComplete = "{0:N0}" -f $EventPercentageComplete Write-Progress -Activity "Analyzing New Relic cluster events of $($ClusterEvents.count)..." -Status "$EventPercentageComplete% Complete" -PercentComplete $EventPercentageComplete -id 1 ########################################################################## $ClusterMatch = $False #Check variable for whether or not this is a new cluster node/instance set $NodesFound = 0 #Iterating through the list of events with cluster-related data foreach ($ReturnCluster in $ReturnClusters) { #If the first node name matches then we have a match. The list of cluster nodes should be identical in every node of a cluster if ($Event.NodeNames[0] -eq $ReturnCluster.Nodes[0]) { $ClusterMatch = $True $ReturnCluster.CoreNodes+=$Event.DeviceID $ReturnCluster.CoreNodes = $ReturnCluster.CoreNodes| select -uniq } } #If we have a cluster match, add the device ID from Core to the list of CoreNodes if (!($ClusterMatch)) { $ClusterObj = [PSCustomObject]@{ status = $Event.ClusterStatus; Nodes = $Event.NodeNames; CoreNodes = @(); NodeStatuses = $Event.NodeStatuses; IPs = $Event.ClusterIPs; IPOwners = $Event.IPOwners; IPNetNames = $Event.ClusterIPNetNames; IPResNames = $Event.ClusterIPResNames; IPSubnets = $Event.ClusterIPSubnets; ResTotal = $Event.ClusterResTotal; OfflineResNames = $Event.ClusterOfflineNames; OfflineResStates = $Event.ClusterOfflineStates; OfflineResTypes = $Event.ClusterOfflineStates; timestamp = $Event.timestamp Instances = @() } #Add the current node's Core device number to the list of CoreNodes and add the new record to our array of analyzed clusters $ClusterObj.CoreNodes+=$Event.DeviceID $ReturnClusters+=$ClusterObj } } return $ReturnClusters } ########################################################################################################### Function Process-CoreClusters ($ClusterInstances,$NSIResults,$AccountName) { <# *********************************************************************************************************** Analyzes the cluster instances we've identified from Core and gathers the cluster as Core expects it. *********************************************************************************************************** #> $ReturnClusters = @() #our array to hold cluster node/instance collections $CurrentInstance = 0 #progress variable ########################################################################## #Correcting for the situation where we have only one cluster. The .count method doesn't work here. try { $ClusterInstances.GetUpperBound(0)|out-null $Divide = $ClusterInstances.Count } catch { $Divide = 1 } ########################################################################## # Iterating through the cluster instances we have received foreach ($Instance in $ClusterInstances) { ########################################################################## #Writing progress $CurrentInstance++ $InstancePercentageComplete = ($CurrentInstance/$Divide)*100 $InstancePercentageComplete = "{0:N0}" -f $InstancePercentageComplete Write-Progress -Activity "Analyzing Core results for cluster $CurrentInstance of $Divide for account $AccountName..." -Status "$InstancePercentageComplete% Complete" -PercentComplete $InstancePercentageComplete -id 3 ########################################################################## $NewClusterSet = $True #Check variable for whether or not this is a new cluster node/instance set $ProcessThis = $False #Check variable for whether or not to process this particular instance #Gathering the cluster nodes from Core based on the device number of the instance $CoreNodes = (Get-CoreDevice -Device $Instance._id).attached_devices #Making sure our cluster nodes are Windows devices and are still in service by inspecting the first node we've been given. Flag for processing if true. if ($Instance.device.status -notlike "*No Longer Active*" -and $Instance.device.status -notlike "*ecommissi*") { if ($CoreNodes.count -gt 0) { foreach ($NSIResult in $NSIResults) { if ($CoreNodes[0].load_value -eq $NSIResult._id -and $NSIResult.process.os_type -like "*indow*") { $ProcessThis = $True } } } else { #Writing exceptions write-host write-host "Possible orphan cluster device. Not reporting any nodes behind it from Core API:" -f "red" write-host "`tAccountName:`t`t$AccountName" write-host "`tCluster device number:`t$($Instance._id)" write-host "`tDevice status in Core:`t$($Instance.device.status)" } } #If our nodes are Windows we'll proceed if ($ProcessThis) { #Iterating through the cluster list we've compiled currently to see if this instance is in our list foreach ($ExistingCluster in $ReturnClusters) { #This is our node counter. As we go through the list and check for similarity with existing ones we'll increment the nodes found for each node we find in common $NodesFound = 0 #Iterating through the node list of the current cluster combination foreach ($ExNode in $ExistingCluster.Nodes) { #Iterating through the nodes as returned from Core foreach ($CoreNode in $CoreNodes) { #Checking the devices returned Core against the nodes in the current cluster combination. If we have a match we'll increment our $NodesFound variable if ($CoreNode.load_value -eq $ExNode) { $NodesFound++ } } } #If our nodes match an existing cluster set we'll set the $NewClusterSet variable to $False such that this instance gets added to an existing cluster combination if ($NodesFound -eq $CoreNodes.Count) { $NewClusterSet = $False } } #If this is a new cluster node/instance combination create a PSO and populate it if ($NewClusterSet) { $ClusterObj = [PSCustomObject]@{ Nodes = @(); Instances = @() } #Add in each node foreach ($CoreNode in $CoreNodes) { $ClusterObj.Nodes+=$CoreNode.load_value } #Add in the current instance $ClusterObj.Instances+=$Instance._id #Add the object to the cluster collection array $ReturnClusters+=$ClusterObj } else { #If this is not a new cluster node/instance collection we'll add to and existing one. Iterate through our collected cluster combinations and add to the one we detect has identical nodes foreach ($Obj in $ReturnClusters) { $NodesFound = 0 #Iterate through each node in the existing cluster collections foreach ($ExistingNode in $Obj.Nodes) { #Iterate through the list of nodes as returned from Core foreach ($CoreNode in $CoreNodes) { #If we find a match, increment the nodes found variable if ($ExistingNode -eq $CoreNode.load_value) { $NodesFound++ } } } #If the nodes found are identical then add this instance to the collection if ($NodesFound -eq $Obj.Nodes.count) { $Obj.Instances+=$Instance._id } } } } } return $ReturnClusters } ########################################################################################################### Function Assign-NRClusters ($AcctName,$AcctNum,$ClusterEvents,$NSIResults) { <# *********************************************************************************************************** This function will analyze the values returned by New Relic and filter for the most recently run record. It will return an array of events with the most recent runtimes *********************************************************************************************************** #> $ReturnEvents = @() $CurrentEvent = 0 #progress variable $ClusterFound = $False ########################################################################## #Correcting for errors thrown for single records If ($ClusterEvents.count -lt 1) { $Divide = 1 } else { $Divide = $ClusterEvents.count } foreach ($Cluster in $ClusterEvents) { ########################################################################## #Writing progress $CurrentEvent++ $EventPercentageComplete = ($CurrentEvent/$Divide)*100 $EventPercentageComplete = "{0:N0}" -f $EventPercentageComplete Write-Progress -Activity "Assigning New Relic result $CurrentEvent of $($Divide) for account $AccountName..." -Status "$EventPercentageComplete% Complete" -PercentComplete $EventPercentageComplete -id 2 ########################################################################## foreach ($Device in $NSIResults) { try { if ([string]$Device._id -eq [string]$Cluster.CoreNodes[0] -and $Device.process.os_type -ne "Hyper-V") { if ([string]$Device.account.number -eq [string]$AcctNum) { $ReturnEvents+=$Cluster } } } catch { write-host [string]$Device._id -f "green" write-host [string]$Cluster.CoreNodes -f "yellow" } } } return $ReturnEvents } ########################################################################################################### Function Poll-NewRelic ($MoveGroup,$PollOnly4) { <# *********************************************************************************************************** This function polls New Relic for all data for a move group and we figure the rest out later. *********************************************************************************************************** #> $NRAccount = "824585" #New Relic account number $APIKey = "csziYa40anD1TdU-JEfsh-VTEirWtXQK" #Supplied API key $NonClusterEvents = @() #Array to gather the non-cluster-specific events $ClusterEvents = @() #Array to gather the cluster-specific events $OtherEvents = @() #Array to gather unique "other" events before adding them into the general events $FilteredNonClusterEvents = @() #Array to gather consolidated non-cluster and "other" events $Process = $True #Variable to ensure we only add a secondary record once $AddEvent = $True #Due to different ways data is stored we have this check variable to determine if we add this to the "other" event array $DeviceNotFound = $False ###################################################################################### #Setting up NRQL for cluster query #We need to poll two different times as if we query by milestone.name we get different results than by runtype and vice versa. We have to pull them both and reconcile if ($PollOnly4) { $QueryClusters = "SELECT * FROM Support_Automation where URN = 'Support Automation:Migration Automations:Windows Audit' AND ``RunType`` = '4week' AND ``Move Group`` = '$MoveGroup' AND ``NodeNames`` IS NOT NULL since 60 days ago LIMIT 1000" } else { $QueryClusters = "SELECT * FROM Support_Automation where URN = 'Support Automation:Migration Automations:Windows Audit' AND ``RunType`` = '2week' AND ``Move Group`` = '$MoveGroup' AND ``NodeNames`` IS NOT NULL since 60 days ago LIMIT 1000" } #Setting up the URI for the cluster query $NRURIClusters = "https://insights-api.newrelic.com/v1/accounts/"+$NRAccount+"/query?nrql="+$QueryClusters #Querying New Relic for clusters write-host "`tPolling for cluster events..." -f "cyan" -nonewline $AllClusterEvents = (Invoke-RestMethod -Uri $NRURIClusters -Headers @{"X-Query-Key"="$APIKey"} -ContentType "application/json" -Method GET).results.events write-host "$($AllClusterEvents.count) events found." -f "cyan" ###################################################################################### #Preparing the query for non-cluster events. We have to poll the New Relic API 10 times due to the limited maximum number of results one API query will return (1000) write-host "`tPolling for detected events..." -f "cyan" -nonewline #We're going to iterate ten times, incrementing $i, which controls the deviceID (device number). We poll for device numbers starting with 1, 2, etc. and add up the results as we poll. for ($i = 1; $i -le 10;$i++) { #If we've received the switch to poll only the 4-week results set up the appropriate NRQL query here. if ($PollOnly4) { $QueryOthers = "SELECT * FROM Support_Automation where URN='Support Automation:Migration Automations:Windows Audit' AND ``RunType`` = '4week' AND ``Move Group`` = '$MoveGroup' AND ``DeviceID`` LIKE '$i%' since 60 days ago LIMIT 1000" } else { $QueryOthers = "SELECT * FROM Support_Automation where URN='Support Automation:Migration Automations:Windows Audit' AND ``RunType`` = '2week' AND ``Move Group`` = '$MoveGroup' AND ``DeviceID`` LIKE '$i%' since 60 days ago LIMIT 1000" } #Compiling the URI to use $NRURIOther = "https://insights-api.newrelic.com/v1/accounts/"+$NRAccount+"/query?nrql="+$QueryOthers #Polling NR [Array]$AllOtherEvents+= (Invoke-RestMethod -Uri $NRURIOther -Headers @{"X-Query-Key"="$APIKey"} -ContentType "application/json" -Method GET).results.events } write-host "$($AllOtherEvents.count) events found." -f "cyan" #Sorting the received events by run-time $AllClusterEvents = $AllClusterEvents | Sort-Object timestamp -Descending $AllOtherEvents = $AllOtherEvents | Sort-Object timestamp -Descending ###################################################################################### #Iterating through the cluster events and eliminating any duplicates. Duplicates will occur when run the audit is run multiple times write-host "`tFiltering cluster events..." -f "cyan" -nonewline foreach ($DetectedCEvent in $AllClusterEvents) { $DuplicateEvent = $False #Converting the text fields within the cluster responses from CSV to arrays $DetectedCEvent.PSObject.Properties |ForEach-Object {if ($_.Value -like "*,*" -and $_.Value -notlike "*Installed*") {$_.Value = $_.Value -split ","}} #Converting timestamp from UNIX format to a datetime. The returns can be inconsistent so a try/catch eliminates any user error messages. #If this fails the date is already in the correct format. try { $DetectedCEvent.timestamp = [System.DateTimeOffset]::FromUnixTimeMilliseconds($DetectedCEvent.timestamp) } catch { } ###################################################################################### #Iterating through our gathered items and determining if there is an existing match. As we've sorted our parent array newest first the first item we collect is the most recent. #We can discard any successive events with the same DeviceID. foreach ($ClusterEvent in $ClusterEvents) { if ($ClusterEvent.DeviceID -eq $DetectedCEvent.DeviceID) { $DuplicateEvent = $True } } #If this is not flagged as a duplicate we can add it to our list if (!($DuplicateEvent)) { $ClusterEvents+=$DetectedCEvent } } write-host "$($ClusterEvents.count) events remaining." -f "cyan" ###################################################################################### #Iterating through our non-cluster events and adding relevant ones to our array write-host "`tFiltering non-cluster events..." -f "cyan" -nonewline foreach ($OtherEvent in $AllOtherEvents) { #Converting timestamp from UNIX format to a datetime. The returns can be inconsistent so a try/catch eliminates any user error messages. #If this fails the date is already in the correct format. try { $OtherEvent.timestamp = [System.DateTimeOffset]::FromUnixTimeMilliseconds($OtherEvent.timestamp) } catch { } #Getting the most current run of a process. This variable will be the check variable to eliminate duplicates. $AddEvent = $True ###################################################################################### #Iterating through our gathered items and determining if there is an existing match. If the Device ID matches we check to see if the EventID matches. Event IDs are unique #for each execution of the milestone. For each execution each write to New Relic will have the same EventID. If the EventIDs are not the same we'll check to see if the #timestamp is newer. If it's not then we'll flag the event for discard. foreach ($DetectedEvent in $OtherEvents) { if ($OtherEvent.DeviceID -eq $DetectedEvent.DeviceID) { if ($OtherEvent.EventID -ne $DetectedEvent.EventID) { if ($OtherEvent.timestamp -lt $DetectedEvent.timestamp) { $AddEvent = $False } } } } #If this is flagged for addition we can add it to the array we plan to return to the main body. if ($AddEvent) { $OtherEvents+=$OtherEvent } } write-host "$($OtherEvents.count) events remaining." -f "cyan" ###################################################################################### #Sorting it all by timestamp $OtherEventsClusterEvents = $OtherEventsClusterEvents |sort-object timestamp -Descending ###################################################################################### Write-Verbose -Message "$($AllDetectedEvents.count) New Relic events found." Write-Verbose -Message "$($ClusterEvents.count) cluster events found." Write-Verbose -Message "$($NonClusterEvents.count) non-cluster events found." return $OtherEvents,$ClusterEvents } ########################################################################################################### #Getting start time $Stopwatch = [system.diagnostics.stopwatch]::startNew() ###################################################################################### #Variable Declaration $Clusters = @() $UniqueAccounts = @() $Dir=(Get-Item -Path . -Verbose).FullName #The current directory $EPFile = ([Environment]::GetFolderPath("Desktop"))+"\Etherpad - $MG.txt" #Our Etherpad file $NewAccount = $True #Check variable for whether or not this is a unique account we haven't found yet $IsUAC = $False #Check variable for whether or not this has been flagged as a device with UAC $ISSNet = $False #Check variable for whether or not this has been flagged as a device with no ServiceNet assigned in the destination DC $Other = $False #Check variable for whether or not this has been flagged as a device with an undefined error $IsCluster = $False #Check variable for whether or not this device is a cluster $Reason = "" #Variable to hold our undefined issues $CurrentDevice = 0 #Counter for progress bar $EventsVoyage = @() #Array for non-cluster events $EventsCluster = @() #Array for cluster recon events $collectionName = $($MG.Substring(0,7) -replace '-','_').ToLower() $MG = $MG.ToUpper() ###################################################################################### #Getting the current directory and importing the required modules $Dir=(Get-Item -Path . -Verbose).FullName $CTKModule = [string]((get-item $Dir).Parent.fullname)+"\Modules\CTKCore\CTKCore.psm1" Import-Module $CTKModule -force -DisableNameChecking $TextModule = [string]((get-item $Dir).Parent.fullname)+"\Modules\TextHandlers\TextHandlers.psm1" Import-Module $TextModule -force -DisableNameChecking ###################################################################################### #Checking CORE token write-host "Checking Core token..." -f "cyan" try {$Testing = Get-CoreDevice -Device 879665} catch { write-host "Token expired or invalid. Gathering new token." -f "yellow" Get-CoreToken } ###################################################################################### #Construct the appropriate URI based on MG $URI = "https://dbapi.nsi.rackspace.com/migration/$collectionName/Find" $JSON = @" { "query": { "move_group":"$MG", "deleted":{"`$ne": true} } } "@ write-host "Polling NSI..." -nonewline -foregroundcolor "cyan" $Results = (Invoke-RestMethod -Uri $URI -Body "$JSON" -ContentType "application/json" -Method "POST" -Headers @{"X-Auth"=$Global:Token} -TimeoutSec 120).result $Results = $Results |sort-object -property _id Write-Host "$($Results.count) devices found." -f "cyan" ###################################################################################### #Sanitizing and importing Voyage list. We're going to import the file as copied out of Voyage and remove the non-breaking space and dollar signs. write-host "Polling and processing New Relic events..." -foregroundcolor "cyan" $EventsVoyage,$EventsCluster = Poll-NewRelic $MG $Only4 $EventsCluster = Process-NRClusters $EventsCluster ###################################################################################### #Iterating through the devices returned from NSI write-host "Processing devices..." -foregroundcolor "cyan" ForEach ($Entry in $Results) { $CurrentEntry++ $EntryPercentageComplete = ($CurrentEntry/$Results.count)*100 $EntryPercentageComplete = "{0:N0}" -f $EntryPercentageComplete Write-Progress -Activity "Analyzing device $CurrentEntry of $($Results.count) in move group $MG..." -Status "$EntryPercentageComplete% Complete" -PercentComplete $EntryPercentageComplete #We're going to compile a list of unique accounts and create a structure under that list of devices with various issues ###################################################################################### #Initalizing per-device variables #Assuming that we've not yet seen this particular account $NewAccount = $True #Assuming that none of our error conditions are true $IsUAC = $False $ISSNet = $False $Other = $False $IsCluster = $False $CollectedEvents = @() #Array for collecting events on a per-device basis ###################################################################################### #Checking to see if this is an account we've seen before Foreach ($Account in $UniqueAccounts) { #We'll iterate through our entire list and if we've seen it we'll change our assumption that this is a new account to false if ($Entry.account.number -eq $Account.Number) {$NewAccount = $False} #write-host $Entry.account.number -f "red" } ###################################################################################### #If this is an account we've not yet seen we'll create a structure for it and add it to our list of unique accounts if ($NewAccount) { $AccountPSO = [PSCustomObject]@{ Name = $Entry.account.name; Number = $Entry.account.number; #These arrays will hold our lists of devices with various afflictions NRClusters = @(); CoreClusters = @(); UAC = @(); SNet = @(); Other = @() } $UniqueAccounts+=$AccountPSO } ###################################################################################### #Checking to see if this is a cluster and if it's a device we can filter out #We're filtering out hypervisor, NTclus, MSDTC, Oracle, and MySQL named clusters if ($Entry.device.make_model -like "*Cluster*" -and $Entry.device.make_model -notlike "*Hypervi*" -and $Entry.device.hostname -notlike "*MSDTC*" -and $Entry.device.hostname -notlike "*NTCLUS*" -and $Entry.device.hostname -notlike "*NTClus*" -and $Entry.device.hostname -notlike "*hypclus*" -and $Entry.device.hostname -notlike "*HypClus*" -and $Entry.device.hostname -notlike "*racle*" -and $Entry.device.hostname -notlike "*racle*" -and $Entry.device.hostname -notlike "*inux*" -and $Entry.device.hostname -notlike "*racle*" -and $Entry.device.make_model -notlike "*RAC*" -and $Entry.device.hostname -notlike "*mysql*" ) { #If this meets our criteria we'll flag it as a cluster device for later addition to an account $IsCluster = $True Write-Verbose -Message "`t`t$($Entry._id) identified as a cluster device." } ###################################################################################### #Iterating through the device list we have from Voyage foreach ($VEvent in $EventsVoyage) { #Collecting all the events for a particular device if ([string]$VEvent.DeviceID -eq [string]$Entry._id) { $CollectedEvents+=$VEvent } } #Write-Verbose "`t`t$($CollectedEvents.count) events identified for $($Entry._id)." ###################################################################################### #Checking for the events in alternate format, i.e. with a Failure Reason foreach ($VEvent in $CollectedEvents) { $Reason = $Null $ReasonsFound = 0 if ($vEvent.State -eq "Fail") { if ($VEvent."Failure Reason" -eq "UAC") { Write-Verbose -Message "`t`t$($Entry._id) UAC failure." $IsUAC = $True } elseif ($VEvent."Failure Reason" -eq "ServiceNet") { Write-Verbose -Message "`t`t$($Entry._id) ServiceNet failure." $IsSNet = $True } else { $ReasonsFound++ if ($ReasonsFound -gt 1) { $Reason+=", $($VEvent."Failure Reason")" } else { $Reason = $VEvent."Failure Reason" } $Other = $True Write-Verbose -Message "`t`t$($Entry._id) other failure - $($VEvent."Failure Reason")" } } ###################################################################################### #Checking for events in the first format, i.e. with an Event Severity if ($VEvent.Severity -eq "hard" -or $vEvent.Severity -eq "soft") { if ($VEvent.Message -like "*User Access Control*") { Write-Verbose -Message "`t`t$($Entry._id) UAC failure. Old format." $IsUAC = $True } elseif ($VEvent.Message -like "*ServiceNet*") { Write-Verbose -Message "`t`t$($Entry._id) ServiceNet failure. Old format." $IsSNet = $True } else { Write-Verbose -Message "`t`t$($Entry._id) identified as a failure. Old format." $ReasonsFound++ if ($ReasonsFound -gt 1) { $Reason+=", $($VEvent.message)" } else { $Reason = $VEvent.message } $Other = $True write-Verbose "Other - Old format." } } } ###################################################################################### #Iterating through our list of unique accounts and inserting our device into the account structure appropriately Foreach ($Account in $UniqueAccounts) { #When the device account number matches the account number we're looking we'll check the flag and insert it into the sub-arrays if ($Entry.account.number -eq $Account.Number) { if ($IsCluster) { $Account.CoreClusters+=$Entry } elseif ($IsUAC) { $Account.UAC+=$Entry } elseif ($IsSNet) { $Account.SNet+=$Entry } elseif ($Other) { #If the device doesn't match any of the above we'll gather the information slightly differently in order to gather an error reason $DevicePSO = [PSCustomObject]@{ _id = $Entry._id; Hostname = (ParseLine $Entry.device.hostname 0 "." " ")[0] Reason = $Reason } $Account.Other+=$DevicePSO } } } } ###################################################################################### #Sorting the account list by account name and then sorting the sub categories of error by device number $UniqueAccounts = $UniqueAccounts|sort-object -property Name foreach ($Account in $UniqueAccounts) { $Account.CoreClusters = $Account.CoreClusters|sort-object -property _id $Account.UAC = $Account.UAC|sort-object -property _id $Account.SNet = $Account.SNet|sort-object -property _id $Account.Other = $Account.Other|sort-object -property _id } ###################################################################################### #Writing the header for the Etherpad file Write-EPHeader $EPFile ###################################################################################### #Iterating through our accounts and writing out any pre-migration issues Write-Verbose -Message "`t`t$($UniqueAccounts.Count) unique accounts identified." foreach ($Account in $UniqueAccounts) { ###################################################################################### #If the account has any faults we'll write out the initial header if ($Account.UAC.Count -ne 0 -or $Account.SNet.Count -ne 0 -or $Account.Other.Count -ne 0) { Write-EPAccountHeader $EPFile $Account ###################################################################################### #Writing out the various issues we've identified #Write UAC issues if ($Account.UAC.Count -ne 0) { Write-Verbose -Message "`t`tWriting UAC failures for account $($Account.Number)." Foreach ($UACDev in $Account.UAC) {Write-EPException $EPFile $UACDev "UAC Enabled"} } #Write ServiceNet mismatches if ($Account.SNet.Count -ne 0) { Write-Verbose -Message "`t`tWriting ServiceNet failures for account $($Account.Number)." Foreach ($SNetDev in $Account.SNet) {Write-EPException $EPFile $SNetDev "ServiceNet Mismatch"} } #Write any other, undefined issues if ($Account.Other.Count -ne 0) { Write-Verbose -Message "`t`tWriting non-specific failures for account $($Account.Number)." Foreach ($Other in $Account.Other) {Write-EPException $EPFile $Other $Other.Reason} } ###################################################################################### #Now that we've written out all issues we'll write a spacer between accounts Write-EPAccountSpacer $EPFile } } ###################################################################################### #At this point we've written out all the pre-migration issues so we'll write the header for the post-migration section Write-EPPostHeader $EPFile ###################################################################################### #Iterating through our accounts and writing out any post-migration issues $CurrentAccount = 0 foreach ($Account in $UniqueAccounts) { ###################################################################################### #Correcting for the errors that are thrown when there's only one account in a MG if ($UniqueAccounts.count -lt 1) { $Divide = 1 } else { $Divide = $UniqueAccounts.count } ###################################################################################### #Writing progress $CurrentAccount++ $AccountPercentageComplete = ($CurrentAccount/$Divide)*100 $AccountPercentageComplete = "{0:N0}" -f $AccountPercentageComplete Write-Progress -Activity "Analyzing account $($Account.Name), $CurrentAccount of $($Divide) unique accounts in move group $MG..." -Status "$AccountPercentageComplete% Complete" -PercentComplete $AccountPercentageComplete -id 1 Write-Progress -Activity "Assigning New Relic result 0 of $($Account.CoreClusters.count) for account $($Account.Name)..." -Status "0% Complete" -PercentComplete 0 -id 2 Write-Progress -Activity "Analyzing Core results for cluster 0 of $($Account.CoreClusters.count) for account $($Account.Name)..." -Status "0% Complete" -PercentComplete 0 -id 3 Write-Progress -Activity "Writing cluster 0 of $($Account.CoreClusters.count) for account $($Account.Name)..." -Status "0% Complete" -PercentComplete 0 -id 4 ###################################################################################### #If the account has any clusters we'll write out the initial header $Account.NRClusters = Assign-NRClusters $Account.Name $Account.Number $EventsCluster $Results ###################################################################################### #Correcting for single clusters as returned from New Relic try { $Account.NRClusters.GetUpperBound(0)|out-null $Divide = $Account.NRClusters.Count } catch { $Divide = 1 } if ($Account.NRClusters.length -ne 0) { ###################################################################################### #Writing out the clusters we've identified $CurrentCluster = 0 #Process the accounts to build node/instance collections that exclude Linux nodes and overwrite the existing clusters collection with this data $Account.CoreClusters = Process-CoreClusters $Account.CoreClusters $Results $Account.Name #Write the account header for each account we're going to be writing Write-EPAccountHeader $EPFile $Account #Iterating through our clusters as defined by New Relic foreach ($Cluster in $Account.NRClusters) { #Write progress $CurrentCluster++ $ClusterPercentageComplete = ($CurrentCluster/$Divide)*100 $ClusterPercentageComplete = "{0:N0}" -f $ClusterPercentageComplete Write-Progress -Activity "Writing cluster $CurrentCluster of $Divide for account $($Account.Name)..." -Status "$ClusterPercentageComplete% Complete" -PercentComplete $ClusterPercentageComplete -id 4 #Write the node/instance collection in the appropriate format Write-EPCluster $EPFile $Cluster $Results $Account.CoreClusters $Account.Name } ###################################################################################### #Now that we've written all the clusters we'll write a spacer to separate accounts add-content $EPFile "****************************************************************************" Write-EPAccountSpacer $EPFile } } Invoke-Item $EPFile ###################################################################################### #Timing functions $Stopwatch.stop() write-host write-host "Complete." -foregroundcolor "green" write-host write-host "Execution time: "$Stopwatch.Elapsed.Minutes"minutes"$Stopwatch.Elapsed.Seconds"seconds." -foregroundcolor "green" write-host