Table View of Datastores Mounted in a Cluster

I really wish I knew more about PowerShell and even the correct terminology because then maybe this wouldn’t have been so difficult to figure out. I spent 2 days trying to get this to work properly and it wasn’t until I was writing this post with the garbage version of this script that I discovered a more efficient way of doing things. Not knowing what the different functions I normally use are called made it difficult to google and discover alternate methods to accomplish what I was trying to accomplish.

With that out of the way, let’s talk about what it is I’m trying to accomplish. A while ago I was working with a customer that wanted to manage all their standard Portgroups with PowerShell/PowerCLI. I had the thought back then that a table view that listed all the portgroups in a cluster in columns then all the hosts in that cluster in rows with X’s marking what hosts had which portgroups. This seemed like a good idea, but I had no idea how to accomplish it at the time. Here we are a year later and the idea popped up at work again this time to see what datastores were mounted on which hosts. With a lot more PowerShell’ing under my belt I thought I was up to the task.

Normally I would accomplish something like that using a PowerShell array. That would normally be accomplished by doing the following:

$report = @()
$row = "" | Select Hostname, Datastore01, Datastore02
$row.Hostname = $hostname
$row.Datastore01 = $datastore01.name
$row.Datastore02 = $datastore02.name
$report += $row

This would have been the ideal solution except I needed to pass a dynamic number of datastores per cluster and I wouldn’t know ahead of time the names of these datastores. The goal of any script I write is re-use given that I have multiple clusters and multiple vCenters to manage. What I wasn’t able to figure out with this approach was how to pass an array of datastore names on the “$row = “” | Select Hostname, Datastore01…” line. No matter what I did I couldn’t make it work. This lead me down another, very inefficient path. What I didn’t realize at the time was that I could accomplish the same thing with “New-Object PSObject” and “Add-Member”.

I got this to work, but it would only record the values of the first (or last) host depending on how I added values. This brought me to the point of doing a blank array then creating a second array that I would update values on for each host and add that array to my initial array. This felt sloppy and inefficient because I was repeating lines in the script to create the second array and it felt like it could be done better. Then I thought about doing the same thing, but this time using a count to create a new array then after the first run adding the entries to the first array. A few tests and eventually I figured it out.

Now for the script.

1. Here we define the cluster name, gather all the datastores in that cluster, and then get all the hosts in the cluster as well. I added the check for NFS type because that is what I use in my environment and it eliminates any local datastores that may be present on a host from appearing in the cluster check.

$cluster = "ClusterName"
$datastores = Get-Cluster $cluster | Get-Datastore | Where {$_.Type -eq "NFS"} | Sort Name
$allHosts = Get-Cluster $cluster | Get-VMHost

2. After that we are opening a ForEach loop. Inside that loop we use $count++ to test how many times we’ve run this loop. Since we aren’t using the $count variable anywhere else this has no value. $count++ will increase by one each time starting with the number 1 on the first run.

ForEach ($vmhost in $allHosts) {
$count++

3. The next lines are creating our array and populating some of the data. New-Object PSObject is creating a blank object. We reference this blank object and add a new column with “Add-Member” and a name of “HostName” with the name of the first ESXi host in the cluster being set as the value. Then we’re going to open another ForEach loop to add a column name with each of the datastore names.

$report = New-Object PSObject
$report | Add-Member -MemberType NoteProperty -Name "HostName" -Value $vmhost.Name
ForEach ($ds in $datastore) {

4. If the host doesn’t have the datastore present we are leave the value blank, but if the datastore is present we mark it with an “X”. We have to create a new variable ($getDS in this case) and check for the datastore. Adding “-ErrorAction SilentlyContinue” will allow us to run the script and not see any errors if the datastore is missing, but still capture the data. The “IF (!$getDS)” is checking if the $getDS variable is empty. Once the host has been checked for that datastore we perform another “Add-Member” to add the datastore as a column and add the value if the datastore is present or not.

$getDS = $vmhost | Get-Datastore $ds.Name -ErrorAction SilentlyContinue
IF (!$getDS) {$present = " "} ELSE {$present = "X"}
$report | Add-Member -MemberType NoteProperty -Name $ds.Name -Value $present}

5. At this point we have collected data on only 1 of our hosts. If we ended the loop here all we’d do is overwrite the data we just wrote over and over until we finished with all the hosts. The next part is where we use that $count++ from step 2. If $count equals 1 (the first run) then we create a new object called $newReport based on $report which contains the data from 1 host. On the next loop we increase $count by one (now it’s value is “2”), replace all the data that existed in $report previously with a new host, and take that new object, $newReport, and add $report to it.

IF ($count -eq "1"){$newReport = New-Object PSObject $report} ELSE {[array]$newReport += $report}}

6. Now that all the data is combined we can view it by running $newReport | Format-Table. This gives us the view below and we can see that we have a few datastores not present on some of our hosts.

$newReport | Sort Hostname | Format-Table

a. This data can also be exported to a CSV file when there are more datastores than can be displayed in your powershell console

$newReport | Export-CSV "C:\datastoreReport.csv" -NoTypeInformation

Below is the full code for this script. You can even wrap this in another ForEach loop for every cluster to see them all at once, but if you do that you’ll have to clear out the $report table by doing “$report = ”” and set the count of your count variable to 0 by doing “$count = 0” once your open the ForEach cluster loop.

$cluster = "ClusterName"
$datastores = Get-Cluster $cluster | Get-Datastore | Where {$_.Type -eq "NFS"} | Sort Name
$allHosts = Get-Cluster $cluster | Get-VMHost
ForEach ($vmhost in $allHosts) {
$count++
$report = New-Object PSObject
$report | Add-Member -MemberType NoteProperty -Name "HostName" -Value $vmhost.Name
ForEach ($ds in $datastores) {
$getDS = $vmhost | Get-Datastore $ds.Name -ErrorAction SilentlyContinue
IF (!$getDS) {$present = " "} ELSE {$present = "X"}
$report | Add-Member -MemberType NoteProperty -Name $ds.Name -Value $present}
IF ($count -eq "1"){$newReport = New-Object PSObject $report} ELSE {[array]$newReport += $report}}

$newReport | Sort HostName | Format-Table

Leave a Reply

Your email address will not be published. Required fields are marked *