NetAppSANInfo.ps1

<#
 
.NOTES
 
File Name: NetAppSANInfo.ps1
 
 
.COMPONENT
 
-NetApp PowerShell Toolkit: https://www.powershellgallery.com/packages/NetApp.ONTAP
 
-ImportExcel module: https://www.powershellgallery.com/packages/ImportExcel
 
 
.SYNOPSIS
 
Version:
 
1.1 Changed the script file name for publishing
      Moved to using current NetApp.ONTAP module
      Simplified connection to cluster
 
1.0 original release
 
Known Issues:
 
none
 
.DESCRIPTION
 
Performs a variety of checks against an ONTAP SAN based system and returns the results in an Excel spreadsheet. Following are the checks involved.
 
    1) Compare the initiators defined in each igroup against the actual logged in initiators on each node.
 
    2) Show total number of initiators per LIF.
 
    3) Gather FCP adapter details that might have a best practice impact.
 
    4) Compare selective LUN mapping reporting-nodes against the hosting HA pair, other nodes in the cluster, and nodes no longer in the cluster.
 
 
.PARAMETER Cluster
The cluster management LIF IP address or resolvable DNS name for the cluster to connect to.
 
.PARAMETER Username
Username to use to connect to the cluster.
 
.PARAMETER Password
Password used to connect to the cluster. This is in clear text. Unavailable if the Username parameter is not passed. If this variable is not provided you will be prompted for the password during the script and it will be obfuscated.
 
.PARAMETER VerboseOutput
See full details of the script action in addition to the Excel file output.
 
 
.EXAMPLE
 
.\NetAppSANInfo.ps1
 
Running without any parameters will prompt for all necessary values
 
.EXAMPLE
 
.\NetAppSANInfo.ps1 -Cluster NetApp1 -Username admin -Password MyPassword
 
Connects to the cluster named NetApp1 with the provided credentials.
 
 
.LINK
 
https://community.netapp.com/t5/Virtualization-Articles-and-Resources/SAN-Info-PowerShell-Script/ta-p/161173
 
#>


<#PSScriptInfo
 
.VERSION 1.0
 
.GUID 97972ffa-d4e3-4428-a270-ca71147d72af
 
.AUTHOR mcgue
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES NetApp.ONTAP,ImportExcel
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
.PRIVATEDATA
 
#>


<#
 
.DESCRIPTION
     
    Performs a variety of checks against an ONTAP SAN based system and returns the results in an Excel spreadsheet. Following are the checks involved.
 
    1) Compare the initiators defined in each igroup against the actual logged in initiators on each node.
 
    2) Show total number of initiators per LIF.
 
    3) Gather FCP adapter details that might have a best practice impact.
 
    4) Compare selective LUN mapping reporting-nodes against the hosting HA pair, other nodes in the cluster, and nodes no longer in the cluster.
 
 
#>
 


#region Parameters and Variables
[CmdletBinding(PositionalBinding=$False)]
Param(

  [Parameter(Mandatory=$False)]
   [string]$Cluster,

  [Parameter(Mandatory=$False)]
   [string]$Username,

  [Parameter(Mandatory=$False)]
   [string]$Password,

  [Parameter(Mandatory=$False)]
   [switch]$VerboseOutput

)

#Import modules
If (-Not (Get-Module NetApp.ONTAP)) {
        
    Import-Module NetApp.ONTAP

}

If (-Not (Get-Module ImportExcel)) {
        
    Import-Module ImportExcel

}

#endregion

#region Main Body
If (!$VerboseOutput) {

    $VerboseChoice = $host.ui.PromptForChoice("Do you wish to see verbose output?","Please select full or summary",[System.Management.Automation.Host.ChoiceDescription[]]("&Full","&Summary"),1)

    Switch ($VerboseChoice) {

        0 {$VerboseOutputSelection = $true}

        1 {$VerboseOutputSelection = $false}

    }

} else {

    $VerboseOutputSelection = $true

}

#Connect to the cluster
If ($Cluster.Length -eq 0) {

    $Cluster = Read-host "Enter the cluster management LIF DNS name or IP address"

}

$Cluster = $Cluster.Trim()

If ($Username.Length -eq 0) {

    $Username = ""

}

If ($Password.Length -eq 0) {

    $SecurePassword = ""

} else {

    $SecurePassword = New-Object -TypeName System.Security.SecureString

    $Password.ToCharArray() | ForEach-Object {$SecurePassword.AppendChar($_)}

}

#Preparing credential object to pass
If ($Username.Length -ne 0 -and $SecurePassword.Length -ne 0) {

    $Credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $Username, $SecurePassword

} else {

    $Credentials = $Username

}

Write-Host "Attempting connection to $Cluster"

$ClusterConnection = Connect-NcController -name $Cluster -Credential $Credentials

#Only proceeding with valid connection to cluster
If (!$ClusterConnection) {

    Write-Host "Unable to connect to NetApp cluster, please ensure all supplied information is correct and try again" -ForegroundColor Yellow

    Exit        

}


#Get basic information
$Nodes = Get-NcNode

$NumberOfNodes = $Nodes.Length

$Vservers = Get-NcVserver

$DataVservers = Get-NcVserver | Where-Object {$_.VserverType -eq "data"}

$ONTAPIFullVersion = Get-NcSystemOntapiVersion

$ClusterInformation = Get-NcCluster

$NodeInformation = Get-NcNode

$ExecutionDate = Get-Date

Write-Host "Execution date and time:" $ExecutionDate

Write-Host "Working with cluster:" $ClusterInformation.ClusterName

Write-Host "With serial number of:" $ClusterInformation.ClusterSerialNumber

Write-Host "Which contains the following Nodes:" $NodeInformation.Node

Write-Host "With serial numbers of:" $NodeInformation.NodeSerialNumber

Write-Host ""

$ColorUnknown = "Cyan"

$ColorWrong = "Red"

$ColorRight = "Green"

$ColorWarning = "Yellow"

$DateStamp = get-date -uformat "%Y-%m-%d@%H-%M-%S"
                
$Global:OutputFile = $ClusterInformation.ClusterName + "_" + $DateStamp + ".xlsx"

Function Check-Initiators ($InitiatorCollection) {

    # 1) Compare the initiators definied in each igroup against the actual logged in initiators on each node.
    
    #Each initiator connected to a LIF in the cluster
    $InitiatorConnectedInfoArray = @()

    #Data to export to Excel
    $FullCollectedOutput = @()

    #All LIFs for this SVM and protocol
    $AllLIFs = Get-NcNetInterface | Where-Object {$_.DataProtocols -eq $Global:ProtocolType -and $_.Vserver -eq $Global:Vserver}

    #Go through each passed connected initiator for initial detail
    ForEach ($InitiatorDetailInstance in $InitiatorCollection) {
        
        #Although this shows adapter it is actually the LIF the initiators are logged in to
        If ($Global:ProtocolType -eq "FCP") {

            $IniatorConnectedLIF = $InitiatorDetailInstance.Adapter

        }

        #LIF from ISCSI detail
        If ($Global:ProtocolType -eq "ISCSI") {

            $IniatorConnectedLIF = $InitiatorDetailInstance.TpgroupName

        }

        #Find the LIF of the connected initiators
        $LIFDetail = Get-NcNetInterface | Where-Object {$_.InterfaceName -eq $IniatorConnectedLIF -and $_.Vserver -eq $Global:Vserver}

        If ($Global:ProtocolType -eq "FCP") {

            ForEach ($IniatorConnection in $InitiatorDetailInstance.FcpConnectedInitiators) {
        
                If ($VerboseOutputSelection) {

                    Write-Host

                    Write-Host "`tFound WWPN: " $IniatorConnection.PortName
            
                    Write-Host "`tUsing LIF: " $LIFDetail.InterfaceName
            
                    Write-Host "`tOn node: " $LIFDetail.CurrentNode

                }
            
                #Add the WWPN, LIF name, and LIF current node to the array
                $InitiatorConnectedInfoArray += , @($IniatorConnection.PortName,$LIFDetail.InterfaceName,$LIFDetail.CurrentNode)
            
            }

        }

        If ($Global:ProtocolType -eq "ISCSI") {

            ForEach ($IniatorConnection in $InitiatorDetailInstance) {
        
                If ($VerboseOutputSelection) {

                    Write-Host

                    Write-Host "`tFound IQN: " $IniatorConnection.InitiatorNodeName
            
                    Write-Host "`tUsing LIF: " $LIFDetail.InterfaceName
            
                    Write-Host "`tOn node: " $LIFDetail.CurrentNode

                }
            
                #Add the IQN, LIF name, and LIF current node to the array
                $InitiatorConnectedInfoArray += , @($IniatorConnection.InitiatorNodeName,$LIFDetail.InterfaceName,$LIFDetail.CurrentNode)
            
            }

        }

    }

    #Details of initiators defined in igroups
    $Igroups = Get-NcIgroup | Where-Object {$_.Vserver -eq $Global:Vserver -and $_.Protocol -eq $Global:ProtocolType}

    ForEach ($Igroup in $Igroups) {

        Write-Host "`tChecking Igroup " $Igroup
        
        $IgroupInitiators = $Igroup.Initiators

        #Sort the results
        $IgroupInitiators = $IgroupInitiators | Sort-Object

        #Find Portsets
        $PortSets = Get-NcPortset | Where-Object {$_.Vserver -eq $Global:Vserver -and $_.InitiatorGroupInfo -eq $Igroup}

        #Check that each initiator from each igroup is logged into each node
        ForEach ($IgroupInitiatorInformation in $IgroupInitiators) {

            #Each LIF where the initiator is logged in
            $LIFsFound = @()

            #Each node that has an initiator logged in
            $NodesFound = @()

            #Each node without the initiator logged in, default to all nodes until a login is found
            $NodesMissing = $Nodes.Node

            $IgroupInitiator = $IgroupInitiatorInformation.InitiatorName

            If ($VerboseOutputSelection) {

                Write-Host "`t`tChecking igroup initiator" $IgroupInitiator

            }

            #Look for an initiator alias
            If ($Global:ProtocolType -eq "FCP") {
            
                $IgroupInitiatorAlias = Get-NcFcpPortNameAlias | Where-Object {$_.Vserver -eq $Global:Vserver -and $_.Wwpn -eq $IgroupInitiator}

            }

            #Keep track of how many nodes the initiator is logged in
            $NodeCounter=0

            #Initiator, LIF, and current node of LIF
            $InitiatorSingleDetailArray = @()

            ForEach ($InitiatorConnectedInfo in $InitiatorConnectedInfoArray) {
            
                #Compare the initiator against the connected initiator
                If ($IgroupInitiator -eq $InitiatorConnectedInfo[0]) {
                
                    $ArrayDetail = @()

                    #Add initiator, LIF, and current node
                    $ArrayDetail = ($IgroupInitiator,$InitiatorConnectedInfo[1],$InitiatorConnectedInfo[2])

                    $InitiatorSingleDetailArray += , $ArrayDetail

                }

            }

            #Compare the actual nodes against where logged in
            ForEach ($InitiatorSingleDetail in $InitiatorSingleDetailArray) {

                #Logged in to at least one node so reset the array
                $NodesMissing = @()

                ForEach ($Node in $Nodes) {

                    #Found the connected node in the nodes list
                    If ($InitiatorSingleDetail[2] -contains $Node) {
                    
                        If ($VerboseOutputSelection) {

                            write-host `t`t$IgroupInitiator "is logged in to" $Node

                        }

                        #Count the node as found
                        $NodeCounter++

                        #Add to the array of Nodes found
                        $NodesFound += $Node.Node

                    }

                }

                ForEach ($Node in $Nodes) {

                    #Set to true by default
                    $NodeIsMissing = $true

                    ForEach ($NodeFound in $NodesFound) {

                        If ($NodeFound -eq $Node.Node) {

                            #reset to $false
                            $NodeIsMissing = $False

                        }

                    }

                    If ($NodeIsMissing) {

                        $NodesMissing += $Node.Node

                    }

                }

                #Checking each LIF out of the total
                ForEach ($SingleLIF in $AllLIFs) {

                    If ($InitiatorSingleDetail[1] -contains $SingleLIF) {

                        If ($VerboseOutputSelection) {

                            write-host `t`t$IgroupInitiator "is using LIF" $SingleLIF

                        }
                        
                        #Check if the LIF belongs to any portset
                        $LIFBelongsToPortset = @()

                        ForEach ($PortSet in $PortSets) {

                            If ($PortSet.PortsetPortInfo -contains $SingleLIF) {

                                $LIFBelongsToPortset += $PortSet.PortsetName

                                If ($VerboseOutputSelection) {

                                    write-host "`t`tFound Portset" $PortSet.PortsetName

                                }

                            }
                            

                        }
                        
                        #Add to the array of Nodes found
                        $LIFsFound += $SingleLIF.InterfaceName

                    }
                
                }

            }

            #Alert and color code for highlighting of findings
            If ($NodeCounter -gt $NumberOfNodes) {

                $HightlightColor = $ColorUnknown

                $InitiatorStatus = "UNKNOWN"

                If ($VerboseOutputSelection) {

                    write-host `t`t$IgroupInitiator "is logged in to more than all nodes" -ForegroundColor $HightlightColor

                }

            }
            
        
            If ($NumberOfNodes -eq $NodeCounter) {

                $HightlightColor = $ColorRight

                $InitiatorStatus = "CORRECT"

                If ($VerboseOutputSelection) {

                    write-host `t`t$IgroupInitiator "is logged in to all nodes" -ForegroundColor $HightlightColor

                }

            }

            If ($NodeCounter -eq 0) {

                $HightlightColor = $ColorWarning

                $InitiatorStatus = "WARNING"

                If ($VerboseOutputSelection) {

                    write-host `t`t$IgroupInitiator "is not logged in to any nodes" -ForegroundColor $HightlightColor

                }

            }

            If ($NodeCounter -lt $NumberOfNodes -and $NodeCounter -ne 0) {

                $HightlightColor = $ColorWrong

                $InitiatorStatus = "INCOMPLETE"

                If ($VerboseOutputSelection) {

                    write-host `t`t$IgroupInitiator "is not logged in to all nodes" -ForegroundColor $HightlightColor

                }

            }

            If ($VerboseOutputSelection) {

                Write-Host

            }

            #Prepare data to send to Excel
            $PowerShellObject = New-Object PSCustomObject
            
            $PowerShellObject | Add-Member -type NoteProperty -name "Igroup" -value $Igroup

            $PowerShellObject | Add-Member -type NoteProperty -name "Initiator" -value $IgroupInitiator

            If ($Global:ProtocolType -eq "FCP") {
            
                $PowerShellObject | Add-Member -type NoteProperty -name "Alias" -value $IgroupInitiatorAlias

            }

            $PowerShellObject | Add-Member -type NoteProperty -name "LIFs" -value ($LIFsFound -Join ",")

            $PowerShellObject | Add-Member -type NoteProperty -name "Portsets" -value ($LIFBelongsToPortset -join ",")

            $PowerShellObject | Add-Member -type NoteProperty -name "Nodes Found" -value ($NodesFound -Join ",")
            
            $PowerShellObject | Add-Member -type NoteProperty -name "Nodes Missing" -value ($NodesMissing -Join ",")

            $PowerShellObject | Add-Member -type NoteProperty -name "Status" -value $InitiatorStatus
            
            $FullCollectedOutput += $PowerShellObject
        
        }

    }

    If ($Igroups.count -gt 0) {

        $WorksheetName = $Global:Vserver.Vserver + "-" + $Global:ProtocolType + "-" + "Initiators"

        $WorksheetStyle = New-ExcelStyle -BorderAround Thin -BorderBottom Thin -BorderTop Thin -BorderLeft Thin -BorderRight Thin

        If ($Global:ProtocolType -eq "FCP") {

            $Range = "H:H"

        }

        If ($Global:ProtocolType -eq "ISCSI") {

            $Range = "G:G"

        }


        $FullCollectedOutput | Export-Excel -Path $Global:OutputFile -WorkSheetname $WorksheetName -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter -Numberformat "#,##0" -Style $WorksheetStyle -ConditionalText $(
            New-ConditionalText -Range $Range -ConditionalType ContainsText CORRECT -ConditionalTextColor white -BackgroundColor $ColorRight
            New-ConditionalText -Range $Range -ConditionalType ContainsText WARNING -ConditionalTextColor purple -BackgroundColor $ColorWarning
            New-ConditionalText -Range $Range -ConditionalType ContainsText UNKNOWN -ConditionalTextColor blue -BackgroundColor $ColorUnknown
            New-ConditionalText -Range $Range -ConditionalType ContainsText INCOMPLETE -ConditionalTextColor black -BackgroundColor $ColorWrong
        )
                
    }

    # 2) Show total number of initiators per LIF.

    #Data to export to Excel
    $FullCollectedOutput = @()

    ForEach ($SingleLIF in $AllLIFs) {

        If ($VerboseOutputSelection) {

            Write-Host "`tChecking" $SingleLIF.InterfaceName "for connected initiators"

        }


        #Default to 0 until counted
        $NumberOfConnectedInitiators = 0

        ForEach ($InitiatorConnectedInfo in $InitiatorConnectedInfoArray) {

            #Found an initiator connected to this LIF
            If ($InitiatorConnectedInfo[1] -eq $SingleLIF.InterfaceName) {

                $NumberOfConnectedInitiators++
            
            }

        }

        #Prepare data to send to Excel
        $PowerShellObject = New-Object PSCustomObject
            
        $PowerShellObject | Add-Member -type NoteProperty -name "LIF" -value $SingleLIF.InterfaceName

        $PowerShellObject | Add-Member -type NoteProperty -name "Number of Connected Initiators" -value $NumberOfConnectedInitiators

        $FullCollectedOutput += $PowerShellObject

    }    

    If ($Igroups.count -gt 0) {

        $WorksheetName = $Global:Vserver.Vserver + "-" + "LIF Counts"

        $WorksheetStyle = New-ExcelStyle -BorderAround Thin -BorderBottom Thin -BorderTop Thin -BorderLeft Thin -BorderRight Thin

        $FullCollectedOutput | Export-Excel -Path $Global:OutputFile -WorkSheetname $WorksheetName -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter -Numberformat "#,##0" -Style $WorksheetStyle

    }
       
}

ForEach ($DataVserver in $DataVservers) {

    Write-Host

    Write-Host "Working with SVM" $DataVserver

    $Global:Vserver = $DataVserver

    #Details of all logged in FCP initiators
    $InitiatorFCPDetails = Get-NcFcpInitiator -VserverContext $DataVserver

    $Global:ProtocolType = "FCP"

    Check-Initiators ($InitiatorFCPDetails)

    #Details of all logged in ISCSI initiators
    $InitiatorISCSIDetails = Get-NcIscsiInitiator -VserverContext $DataVserver
    
    $Global:ProtocolType = "ISCSI"

    Check-Initiators ($InitiatorISCSIDetails)

}

# 3) Gather FCP adapter details that might have a best practice impact.

#Data to export to Excel
$FullCollectedOutput = @()

Write-Host

Write-Host "Currently checking FCP adapters"

$OnlineFCPAdapters = Get-NcFcpAdapter | Where-Object {$_.FabricEstablished -eq $true}

ForEach ($OnlineFCPAdapter in $OnlineFCPAdapters) {

    If ($VerboseOutputSelection) {

        Write-Host "`tFound adapter" $OnlineFCPAdapter.Adapter "on node" $OnlineFCPAdapter.Node

    }

    #Prepare data to send to Excel
    $PowerShellObject = New-Object PSCustomObject

    $PowerShellObject | Add-Member -type NoteProperty -name "Node" -value $OnlineFCPAdapter.Node

    $PowerShellObject | Add-Member -type NoteProperty -name "Adapter" -value $OnlineFCPAdapter.Adapter

    $PowerShellObject | Add-Member -type NoteProperty -name "WWPN" -value $OnlineFCPAdapter.PortName

    $PowerShellObject | Add-Member -type NoteProperty -name "Switch Port" -value $OnlineFCPAdapter.SwitchPort

    #MaxSpeed and DataLinkRate report as a number but the configured speed reports with "Gb" at the end so bringing these into parity
    $MaxSpeedWithGB = $OnlineFCPAdapter.MaxSpeed.tostring()+"Gb"

    $PowerShellObject | Add-Member -type NoteProperty -name "Maximum Speed" -value $MaxSpeedWithGB

    $PowerShellObject | Add-Member -type NoteProperty -name "Configured Speed" -value $OnlineFCPAdapter.Speed

    $CurrentSpeedWithGB = $OnlineFCPAdapter.DataLinkRate.tostring()+"Gb"

    $PowerShellObject | Add-Member -type NoteProperty -name "Current Speed" -value $CurrentSpeedWithGB

    If ($OnlineFCPAdapter.MaxSpeed -gt $OnlineFCPAdapter.DataLinkRate) {

        $PowerShellObject | Add-Member -type NoteProperty -name "Capability Usage" -value "PARTIAL"

    } else {

        $PowerShellObject | Add-Member -type NoteProperty -name "Capability Usage" -value "FULL"
    }
    
    #Replace the alpha characters but leave the numerals and the period
    $SfpRxPowerNumeral = $OnlineFCPAdapter.SfpRxPower -replace "[^0-9.]"

    $PowerShellObject | Add-Member -type NoteProperty -name "SFP Receive Optical Power" -value $SfpRxPowerNumeral

    $SfpTxPowerNumeral = $OnlineFCPAdapter.SfpTxPower -replace "[^0-9.]"

    $PowerShellObject | Add-Member -type NoteProperty -name "SFP Transmit Optical Power" -value $SfpTxPowerNumeral

    $FullCollectedOutput += $PowerShellObject

}

If ($OnlineFCPAdapters.count -gt 0) {

    $WorksheetName = "FCP Adapters"

    $WorksheetStyle = New-ExcelStyle -BorderAround Thin -BorderBottom Thin -BorderTop Thin -BorderLeft Thin -BorderRight Thin

    $RangeSpeedCapability = "H:H"

    #Since using a "less than" operation, it will mark all the empty cells this background color unless we limit the range to just the number of entries
    $RangeSFPReceive = "I1:I"+$FullCollectedOutput.count

    $RangeSFPTransmit = "J1:J"+$FullCollectedOutput.count

    #Highlight the adapters not using the full capability. Also hightlight optical power below 300 which matches Active IQ health summary alert.
    $FullCollectedOutput | Export-Excel -Path $Global:OutputFile -WorkSheetname $WorksheetName -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter -Numberformat "#,##0" -Style $WorksheetStyle -ConditionalText $(
            New-ConditionalText -Range $RangeSpeedCapability -ConditionalType ContainsText FULL -ConditionalTextColor white -BackgroundColor green
            New-ConditionalText -Range $RangeSpeedCapability -ConditionalType ContainsText PARTIAL -ConditionalTextColor purple -BackgroundColor yellow
            New-ConditionalText -Range $RangeSFPReceive -ConditionalType LessThan 300 -ConditionalTextColor purple -BackgroundColor yellow
            New-ConditionalText -Range $RangeSFPTransmit -ConditionalType LessThan 300 -ConditionalTextColor purple -BackgroundColor yellow
            )

}

# 4) Compare selective LUN mapping reporting-nodes against the hosting HA pair, other nodes in the cluster, and nodes no longer in the cluster.

ForEach ($DataVserver in $DataVservers) {

    Write-Host

    Write-Host "Working with SVM" $DataVserver

    $Global:Vserver = $DataVserver

    #Data to export to Excel
    $FullCollectedOutput = @()

    $LunMaps =  Get-NcLunMap -VserverContext $Global:Vserver

    Write-Host "`tCurrently checking LUN maps"

    ForEach ($LunMap in $LunMaps) {

        If ($VerboseOutputSelection) {

            Write-Host "`tFound LUN" $LunMap.Path "on node" $LunMap.Node "with reporting-nodes" $LunMap.ReportingNodes

        }

        #Create array of actual reporting-nodes
        $ArrayHAPairReportingNodes = @()

        $ArrayClusterReportingNodes = @()

        $ArrayUnknownReportingNodes = @()

        #Get the node where the LUN resides
        $LunHostingNode = $LunMap.Node

        #Get the name of the partner of this node hosting the LUN
        $LunHostingNodePartner = Get-NcClusterHaPartner ($LunHostingNode)

        #See if the HA pair is represented in the reporting nodes
        If ($LunMap.ReportingNodes -contains $LunHostingNode -and $LunMap.ReportingNodes -contains $LunHostingNodePartner) {

            $ReportingNodesContainHAPair = $true

        } else {

            $ReportingNodesContainHAPair = $False

        }

        ForEach ($ReportingNode in $LunMap.ReportingNodes) {

            #Check if reporting node is a) the owner of LUN or HA partner, b) another node in the cluster, or c) an unknown node
            If ($ReportingNode -eq $LunHostingNode -or $ReportingNode -eq $LunHostingNodePartner.Partner) {

                #Found a node in the HA pair owning the LUN
                $ArrayHAPairReportingNodes += $ReportingNode

            } else {

                If ($Nodes.Node -contains $ReportingNode) {

                    #Found another node in the cluster
                    $ArrayClusterReportingNodes += $ReportingNode

                } else {

                    #Found a node or ghost node that currently is not part of the cluster
                    $ArrayUnknownReportingNodes += $ReportingNode

                }

            }

        }

        #Prepare data to send to Excel
        $PowerShellObject = New-Object PSCustomObject
            
        $PowerShellObject | Add-Member -type NoteProperty -name "LUN" -value $LunMap.Path

        $PowerShellObject | Add-Member -type NoteProperty -name "Home Node" -value $LunMap.Node

        $PowerShellObject | Add-Member -type NoteProperty -name "Reporting Nodes from HA Pair" -value ($ArrayHAPairReportingNodes -Join ",")

        $PowerShellObject | Add-Member -type NoteProperty -name "Reporting Nodes from Other Cluster Nodes" -value ($ArrayClusterReportingNodes -Join ",")

        $PowerShellObject | Add-Member -type NoteProperty -name "Reporting Nodes from Unknown Nodes" -value ($ArrayUnknownReportingNodes -Join ",")

        $FullCollectedOutput += $PowerShellObject

    }

    If ($LunMaps.count -gt 0) {

        $WorksheetName = $Global:Vserver.Vserver + "-" + "LUN Maps"

        $WorksheetStyle = New-ExcelStyle -BorderAround Thin -BorderBottom Thin -BorderTop Thin -BorderLeft Thin -BorderRight Thin

        #Highlight the adapters not using the full capability. Also hightlight optical power below 300 which matches Active IQ health summary alert.
        $FullCollectedOutput | Export-Excel -Path $Global:OutputFile -WorkSheetname $WorksheetName -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter -Numberformat "#,##0" -Style $WorksheetStyle

    }

}

Write-Host

Write-Host "See output in file:" $Global:OutputFile

#endregion