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 |