PowerCLI: Show HBA Path Status

When you have a lot of hosts, with a lot of LUNs, it can be difficult to keep abreast of the status of the paths for them. I have encountered issues where a path was unknowingly marked as dead, plus it’s generally a good idea to ensure that your storage paths are actually available.

Consequentially, I searched for a PowerCLI script that would give me a simple report of the status of each of the LUN paths to each of the HBAs on my hosts. I found John Milner’s post to be very helpful, and it gave me exactly the results that I wanted. However, it took forever to execute…almost 30 minutes for just one of my clusters (to be fair, that cluster has 12 hosts with > 100 LUNs and two paths to each).

Using his script as an example, and keeping a good bit of the formatting code, I have modified his script to use views of the host objects and cull the information from there. This makes it significantly faster…what took 28 minutes before now takes about 30 seconds.

The result looks exactly like the original:

This is also helpful, for me, because each of my hosts should have the same number of active and standby paths…if there is more of one or the other, that’s an indication that a path is missing, or there is an extra for some reason. For example, the above is a single cluster, so they should all be identical. Multipathing appears to be good because there are the same number of active and standby paths. However, one host has an extra pair of paths…a simple check revealed that an old LUN had not been removed…no damage done, but very easy to spot with this script.

Thanks to John for doing all the hard work with formatting!

19 thoughts on “PowerCLI: Show HBA Path Status”

    • Hi Vcpguy,

      You can limit to a single cluster by adjusting the Get-View line at the very top to include a SearchRoot parameter. For example:

      Get-View -ViewType "HostSystem" -Property Name,Config.StorageDevice -SearchRoot (Get-Cluster "some Cluster" | Get-View).MoRef

      The above will limit the hosts to a cluster with the name “some Cluster”.

      Hope that helps and thanks for reading!


  1. getting below error while running

    ConvertFrom-Csv : Cannot validate argument on parameter ‘InputObject’. The argu
    ment is null, empty, or an element of the argument collection contains a null v
    alue. Supply a collection that does not contain any null values and then try th
    e command again.
    At C:UsersmurtiDesktopHBA.PS1:34 char:82
    + ConvertFrom-Csv -Header “VMHost”, “HBA”, “Active”, “Dead”, “Standby” -InputOb
    ject <<<< $result | ft -AutoSize
    + CategoryInfo : InvalidData: (:) [ConvertFrom-Csv], ParameterBin
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.Power

  2. I’m having an issue with this script duplicating the total # of paths. Have you seen this before? Also how could I get the cluster info back like John’s original script?


    • Andrew,

      The only thing that comes to mind is if each HBA sees two, or more, paths to the LUNs. Aside from that, I’m drawing a blank. Can you give any more details of your environment?

      Thanks for reading,


  3. What if your path selection is round robin, are you still supposed to see the same number of Active and StandBy paths?

  4. Hi All,

    I received the below error and I edited the last time as listed below and the script worked. Thanks.

    Error :
    “Microsoft.PowerShell.Commands.Internal.Format.FormatStartData” is
    not legal or not in the correct sequence. This is likely caused by a user-specified “format-table”
    command which is conflicting with the default formatting.
    + CategoryInfo : InvalidData: (:) [out-lineoutput], InvalidOperationException
    + FullyQualifiedErrorId : ConsoleLineOutputOutOfSequencePacket,Microsoft.PowerShell.Commands.O

    Change code :

    ConvertFrom-Csv -Header “VMHost”, “HBA”, “Active”, “Dead”, “Standby” -InputObject $result | Format-Table -AutoSize | Out-Default

  5. Sorry for the necro-post here, but if anyone is experiencing an issue where the total number of paths is more than expected, it’s because you have multiple paths to the same LUN (for example, through 2 different fabrics, or through 2 different controller heads). In my case, I was seeing a quadrupling of the expected paths as my fabrics are very redundant.

    The fix for this is to change this code:

    $_.Target | %{
    $_.Lun | %{
    $id = $_.ScsiLun

    $multipathInfo = $view.Config.StorageDevice.MultipathInfo.Lun | ?{ $_.Lun -eq $id }

    $a = [ARRAY]($multipathInfo.Path | ?{ $_.PathState -like "active" })
    $s = [ARRAY]($multipathInfo.Path | ?{ $_.PathState -like "standby" })
    $d = [ARRAY]($multipathInfo.Path | ?{ $_.PathState -like "dead" })

    $active += $a.Count
    $standby += $s.Count
    $dead += $d.Count

    to simply this:

    $multipathInfo = $view.Config.StorageDevice.MultipathInfo.Lun | ? { $_.Path.Adapter -like "*FibreChannelHba*" }

    $a = [ARRAY]($multipathInfo.Path | ?{ $_.PathState -like "active" })
    $s = [ARRAY]($multipathInfo.Path | ?{ $_.PathState -like "standby" })
    $d = [ARRAY]($multipathInfo.Path | ?{ $_.PathState -like "dead" })

    $active += $a.Count
    $standby += $s.Count
    $dead += $d.Count

    The extra foreach loops (the %{ blocks, for those curious) end up causing the script to double, triple, quadruple (or more, depending on the actual number of paths to each LUN) the count of actual paths.

    Also note the duplicate adapter filter here; if your host has additional onboard adapters for things like the local drive array, it’ll show up as an extra path where you may or may not care (remove everything after the pipe in the $multipathInfo line if you do).

    • Hello Prabhu,

      Since the result is already technically a CSV before it’s formatted for display, all you would need to do is pipe $result to Out-File at the end.

      # set the result to a file
      $result | Out-File -Filepath .\result.csv

      Hope that helps.


  6. Hi all,

    I found this page very helpful and used the script to help me with identifying path states during a update of our disk array. However, I did notice (as others have too) that I seemed to be getting too many paths returned in my output. So I took some time working out what the issue was with the script. I ended up re-writing the script to use “foreach” instead of foreach-object, just personal preference really. My script seems to return the right number of paths now.

    #connect to vIServers
    if ($global:DefaultVIServers.Count -ne 2) {
    Connect-vIServer -Server server01.my.domain
    Connect-vIServer -Server server02.my.domain
    else {
    Write-Host “Already connected to” $global:DefaultVIServers -ForegroundColor Green

    #get all views from all datacentres
    $views = @()
    foreach ($server in $global:DefaultVIServers) {
    $views += Get-View -ViewType “HostSystem” -Property Name, Config.StorageDevice -Server $server.name
    $views = $views | sort name

    #build array and export to clipboard
    $index = 0
    $array = @()
    foreach ($view in $views) {
    $index ++
    write-host “Processing” $view.name $index “of” $views.Count -ForegroundColor Yellow
    $hbas = $view.Config.StorageDevice.ScsiTopology.Adapter |?{ $_.Adapter -like “*FibreChannelHba*” }

    write-host `t “Found” $hbas.count “HBAs” -ForegroundColor DarkYellow

    foreach ($adapter in $hbas) {
    $hba = $null
    $hba = $adapter.adapter.split(“-“)[2]
    $state = $null
    $state = @()
    $table = “” | select ESXHostName, Cluster, HBA_Adapter, HBA_Status, HBA_TargetPathCount, ActiveCount, DeadCount, StandbyCount
    $table.ESXHostName = $View.Name
    $table.HBA_Adapter = $hba
    foreach ($target in $adapter.Target) {
    write-host `t`t “Target:” $target.Target -ForegroundColor Magenta
    foreach ($lun in $target.Lun) {
    #write-host `t `t `t $lun.Key -ForegroundColor DarkMagenta
    $id = $lun.ScsiLun.Split(“-“)[2]
    $multipathInfo = $view.Config.StorageDevice.MultipathInfo.Lun | Where-Object { $_.id -eq $id }
    $state += $multipathInfo.Path | Where-Object {$_.adapter -like “*$hba*”}
    $grouped = $state | Group-Object state
    $table.Cluster = (get-cluster -VMHost $view.Name).name
    $table.HBA_Status = (Get-VMHostHba -VMHost $view.Name -Device $hba).Status
    $table.HBA_TargetPathCount = $adapter.Target.Count
    $table.ActiveCount = ($grouped| Where-Object {$_.name -like “*active*”}).count
    $table.DeadCount = ($grouped| Where-Object {$_.name -like “*dead*”}).count
    $table.StandbyCount = ($grouped| Where-Object {$_.name -like “*stand*”}).count
    $array += $table
    $array | ft -AutoSize

    $array | ConvertTo-Csv -Delimiter “`t” -NoTypeInformation | clip.exe


Leave a Reply