Using PowerShell to create a vCloud Director Tenant HTML Report

As we all know, VMware vCloud Director is a widely adopted IaaS platform for the service provider market. VMware vCloud Director offers a self-service web portal to manage your vApps, VMs, networks, and network functions (Edge Firewall, NAT, VPN, Load Balancer, DFWand Rrouting). But there is also a RESTful API, and a PowerShell Module offered for the administrators and tenants. With the help of the API, some third– party vendors offer an extended web portal (for example, OnApp). In this article, I’ll show you how to use the VMware vCloud Director PowerShell Module (part of the famous VMware PowerCLI) to extend the default UI with a vCloud Director Tenant HTML Report for your most important objects. Unfortunately, there is no reporting option offered by the self-service web portal itself.

The problem with extensive HTML reports created with PowerShell is that the ConvertTo-HTML Cmdlet is not really flexible. So I was looking for alternative ways and found the PowerShell module PowerStartHTML from Timothy Dewin. This module combines PowerShell with Bootstrap, a open source toolkit for developing with HTML, CSS, and JS. With this toolkit, I was able to create a report that contains the necessary information’s in a nicelooking format.

vCloud Director Tenant HTML Report

VMware PowerCLI for vCloud Director Basics

The latest version of VMware PowerCLI is available on PowerShell Gallery. So if you use PowerShell 5.0 or newer, you can install PowerCLI modules with one simple command:

Install-Module VMware.PowerCLI -Scope CurrentUser

After installation is successful, you can load all VMware modules using this command:

Get-Module -Name VMware* -ListAvailable | Import-Module

One of the loaded modules is VMware.VimAutomation.Cloud, which is for VMware vCloud Director. But be aware that not all commands work as a tenant. Some Ccmdlets are for administrative use only and others are for vCloud Air (which was sold to OVH).

All available vCloud Director Cmdlets (and one function) on version 6.5.1:

vCloud PowerCLI Cmdlets

The aliases for vCloud Air are removed from this screenshot. 

To connect to your VMware vCloud Director organization, the Cmdlet Connect-CIServer works as a provider administrator and also as a tenant. Tenants simplneed to add their organization during connection:

Connect-CIServer -Server <vCD FQDN> -Org <Org Name> [-Credential <PS Credential>]

Other PowerCLI vCloud Director examples:

Some interesting Cmdlets

Search-Cloud

The Search-Cloud Cmdlet is the fastest way to get vCloud Director objects via PowerCLI. You can also list some objects for that since no typical Get-* command is available (for example, Edge Gateways).

Example: List Edge Gateways:

Search-Cloud -QueryType EdgeGateway

All QueryTypes can be found in the VMware vSphere 6.5 Documentation Center.

Get-CIVApp

The Get-CIVApp Cmdlet lists all vCloud Director vApps. vApps are also known from VMware vSphere but are way more important in vCloud Director. A vApp can contain objects like VMs, networks, and network functions.

Example: List vApp ‘NetApp-HA’:

Get-CIVApp -Name NetApp-HA

Get-CIVM

The Get-CIVM Cmdlet lists all vCloud Director VMs. VMs are in vCloud Director always children from vApps and also the Cmdlet can use Get-CIVApp as piped input.

Example: List all VMs from vApp ‘NetApp-HA’:

Get-CIVApp -Name NetApp-HA | Get-CIVM

Bootstrap with PowerShell Basics

Firs I have to say I am no HTML pro and I would not have been able to create the report without the help of this great example from Timothy Dewin. Timothy created an awesome Veeam Backup & Replication HTML Report with his PowerStartHTML  PowerShell Module.

The function New-PowerStartHTML creates a new object with the necessary methods to create a full Bootstrap HTML page. The newly created object already includes the stylesheet for Bootstrap.

The two main methods to create your HTML page are:

$ps.Add(type,class,text)
$ps.Append(type,class,text)

Here’s an explanation from the README on GitHub:

The difference between Add is that it will remember the new element as the as the element. With Append, it remember the parent, so you can add an element at the same depth.

All the possible types and classes can be found in the Bootstrap documentation.

To get started with a simple HTML file, I created a report for all available modules on my PC. The main part of the report is a simple table.

$Modules = Get-Module -ListAvailable
$Path = "C:\Temp\Example.html"
$ps = New-PowerStartHTML -title "PowerStartHTML Example"
$ps.cssStyles['.bgtitle'] = "background-color:grey"
$ps.Main().Add("div","jumbotron").N()
$ps.Append("h1","display-3",("Module Report")).Append("p","lead","Module
Count: {0}" -f $Modules.Count).Append("p","font-italic","This Report lists all
Available PowerShell").N()
$ps.Main().Append("h2",$null,"Modules").N()
$ps.Add('table','table').Add("tr","bgtitle textwhite").Append("th",$null,"Module Name").Append("th",$null,"Module
Version").Append("th",$null,"Module Type").N()
foreach ($Module in $Modules) {
$ps.Add("tr").N()
$ps.Append("td",$null,$Module.Name).N()
$ps.Append("td",$null,$Module.Version).N()
$ps.Append("td",$null,$Module.ModuleType).N()
$ps.Up().N()
}$
ps.Up().N()
$ps.save($Path)
Start-Process $Path

simple Bootstrap example

This Eexample is not very complex and it maybe also be possible to create a simple table like this with the ConvertTo-HTML Cmdlet. But let’s get a little bit more complex and add the commands of each Module in a sub-section of the table.

$Modules = Get-Module -ListAvailable
$Path = "C:\Temp\Example.html"
$ps = New-PowerStartHTML -title "PowerStartHTML Example"
$ps.cssStyles['.bgsubsection'] = "background-color:#eee;"
$ps.cssStyles['.bgtitle'] = "background-color:grey"
$ps.Main().Add("div","jumbotron").N()
$ps.Append("h1","display-3",("Module Report")).Append("p","lead","Module
Count: {0}" -f $Modules.Count).Append("p","font-italic","This Report lists all
Available PowerShell").N()
$ps.Main().Append("h2",$null,"Modules").N()
$ps.Add('table','table').Add("tr","bgtitle textwhite").Append("th",$null,"Module Name").Append("th",$null,"Module
Version").Append("th",$null,"Module Type").N()
foreach ($Module in $Modules) {
$ps.Add("tr").N()
$ps.Append("td",$null,$Module.Name).N()
$ps.Append("td",$null,$Module.Version).N()
$ps.Append("td",$null,$Module.ModuleType).N()
$ps.Up().N()$ps.Add("td","bgsubsection").N()
$ps.Add("table","table bgcolorsub").N()
$ps.Add("tr").N()
$headers = @("Command Type","Name")
foreach ($h in $headers) {
$ps.Append("th",$null,$h).N()
}
$ps.Up().N()
[Array] $Commands = Get-Command -Module $Module.Name
foreach ($Command in $Commands) {
$ps.Add("tr").N()
$ps.Append("td",$null,$Command.CommandType).N()
$ps.Append("td",$null,$Command.Name).N()
$ps.Up().N()
}
$ps.Up().Up().N()
}
$ps.Up().N()
$ps.save($Path)
Start-Process $Path

Advanced Bootstrap Example

Okay, so that table is definitely more complex than what you can create with the ConvertTo-HTML Ccmdlet!

The vCloud Director Tenant HTML Report

Now that we’re familiar with the VMware PowerCLI vCloud Director Module and the basics of Bootstrap PowerShell handling, let`s get started with the report.

The first step is to decide which data is necessary for the vCloud Director Tenant HTML Report. This is my selection:

vCloud Director Tenant HTML Report - Design

With this set of data, we are now able to collect the object details from our VMware vCloud Director server using VMware PowerCLI.

Object details

Get Users

[Array] $Users = Get-CIUser

Get Catalogs

[Array] $Catalogs = Get-Catalog

Get Catalog Items

[Array] $Items = $Catalog.ExtensionData.CatalogItems.CatalogItem

Get VDCs

[Array] $OrgVdcs = Get-OrgVdc

Get VDC Edge Gateways

[Array] $Edges = Search-Cloud -QueryType EdgeGateway -Filter "Vdc==$($OrgVdc.Id)"

Get VDC Networks

[Array] $Networks = $OrgVdc | Get-OrgVdcNetwork

Get VDC vApps

[Array] $Vapps = $OrgVdc | Get-CIVApp

Get VDC vApp VMs

[Array] $VMs = $Vapp | Get-CIVM

Final vCloud Director Tenant HTML Report

I wanted to ship the vCloud Director Tenant HTML Report script as a PowerShell Module since that is the best way to also include the PowerStartHTML Module.

How to Create a Module

To create my PowerShell modules in a standardized way I use the Plaster Module with a customized template. For more details about Plaster and Plaster templates, you can read my blog article.

vCloud Director Tenant HTML Report - Plaster

The ‘VMware-vCD-TenantReport’ Module

With my Plaster template and some manual modifications, I have created a Module with PowerStartHTML as a Sub-Module. The module also ships with a basic Pester test.

vCloud Director Tenant HTML Report - Module

The Report Script

The script to create the vCloud Director Tenant HTML Report is embedded in the exported Get-VcdTenantReport function, The usage is quite simple:

Get-VcdTenantReport -Server <vCD FQDN> -Org <Org Name> [-Credential $PSCred -Path C:\temp\report.html]

vCloud Director Tenant HTML Report - Get-VcdTenantReport

The report will be automatically opened in your default browser.

vCloud Director Tenant HTML Report PowerShell Code
function Get-VcdTenantReport {
<#
    .NOTES
    ===========================================================================
    Created by: Markus Kraus
    Twitter: @VMarkus_K
    Private Blog: mycloudrevolution.com
    ===========================================================================
    Changelog:
    1.0.0 - Inital Release
    1.0.1 - Removed "Test-IP" Module
    1.0.2 - More Detailed Console Log
    ===========================================================================
    External Code Sources:
    Examle Usage of BOOTSTRAP with PowerShell
    https://github.com/tdewin/randomsamples/tree/master/powershell-veeamallstat
    BOOTSTRAP with PowerShell
    https://github.com/tdewin/randomsamples/tree/master/powerstarthtml
    ===========================================================================
    Tested Against Environment:
    vCD Version: 8.20
    PowerCLI Version: PowerCLI 6.5.1
    PowerShell Version: 5.0
    OS Version: Windows 8.1
    Keyword: VMware, vCD, Report, HTML
    ===========================================================================

    .DESCRIPTION
    This Function creates a HTML Report for your vCloud Director Organization.

    This Function is fully tested as Organization Administrator.
    With lower permissions a unexpected behavior is possible.

    .Example
    Get-VcdTenantReport -Server $ServerFQDN -Org $OrgName -Credential $MyCedential

    .Example
    Get-VcdTenantReport -Server $ServerFQDN -Org $OrgName -Path "C:\Temp\Report.html"

    .PARAMETER Server
    The FQDN of your vCloud Director Endpoint.

    .PARAMETER Org
    The Organization Name.

    .PARAMETER Credential
    PowerShell Credentials to access the Eénvironment.

    .PARAMETER Path
    The Path of the exported HTML Report.

#>
#Requires -Version 5
#Requires -Modules VMware.VimAutomation.Cloud, @{ModuleName="VMware.VimAutomation.Cloud";ModuleVersion="6.5.1.0"}

[CmdletBinding()]
param(
    [Parameter(Mandatory=$True, ValueFromPipeline=$False)]
    [ValidateNotNullorEmpty()]
        [String] $Server,
    [Parameter(Mandatory=$True, ValueFromPipeline=$False)]
    [ValidateNotNullorEmpty()]
        [String] $Org,
    [Parameter(Mandatory=$False, ValueFromPipeline=$False)]
    [ValidateNotNullorEmpty()]
        [PSCredential] $Credential,
    [Parameter(Mandatory=$false, ValueFromPipeline=$False)]
    [ValidateNotNullorEmpty()]
        [String] $Path = ".\Report.html"

)

Process {

    # Start Connection to vCD

    if ($global:DefaultCIServers) {
        "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") - Disconnect existing vCD Server ..."
        $Trash = Disconnect-CIServer -Server * -Force:$true -Confirm:$false -ErrorAction SilentlyContinue
    }

    "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") - Connect vCD Server ..."
    if ($Credential) {
        $Trash = Connect-CIServer -Server $Server -Org $Org -Credential $Credential -ErrorAction Stop
    }
    else {
        $Trash = Connect-CIServer -Server $Server -Org $Org -ErrorAction Stop
    }
    "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") - Create HTML Report..."

    # Init HTML Report
    $ps = New-PowerStartHTML -title "vCD Tenant Report"

    #Set CSS Style
    $ps.cssStyles['.bgtitle'] = "background-color:grey"
    $ps.cssStyles['.bgsubsection'] = "background-color:#eee;"

    # Processing Data
    ## Get Main Objects
    [Array] $OrgVdcs = Get-OrgVdc
    [Array] $Catalogs = Get-Catalog
    [Array] $Users = Get-CIUser

    ## Add Header to Report
    $ps.Main().Add("div","jumbotron").N()
    $ps.Append("h1","display-3",("vCD Tenant Report" -f $OrgVdcs.Count)).Append("p","lead","Organization User Count: {0}" -f $Users.Count).Append("p","lead","Organization Catalog Count: {0}" -f $Catalogs.Count).Append("p","lead","Organization VDC Count: {0}" -f $OrgVdcs.Count).Append("hr","my-4").Append("p","font-italic","This Report lists the most important objects in your vCD Environmet. For more details contact your Service Provider").N()

    ## add Org Users to Report
    $ps.Main().Append("h2",$null,"Org Users").N()

    $ps.Add('table','table').Add("tr","bgtitle text-white").Append("th",$null,"User Name").Append("th",$null,"Locked").Append("th",$null,"DeployedVMCount").Append("th",$null,"StoredVMCount").N()

    foreach ($User in $Users) {
        $ps.Add("tr").N()
        $ps.Append("td",$null,$User.Name).N()
        $ps.Append("td",$null,$User.Locked).N()
        $ps.Append("td",$null,$User.DeployedVMCount).N()
        $ps.Append("td",$null,$User.StoredVMCount).N()
        $ps.Up().N()

    }
    $ps.Up().N()

    ## add Org Catalogs to Report
    $ps.Main().Append("h2",$null,"Org Catalogs").N()

    foreach ($Catalog in $Catalogs) {
        $ps.Add('table','table').Add("tr","bgtitle text-white").Append("th",$null,"Catalog Name").N()
        $ps.Add("tr").N()
        $ps.Append("td",$null,$Catalog.Name).Up().N()

        $ps.Add("td","bgsubsection").N()
        $ps.Add("table","table bgcolorsub").N()
        $ps.Add("tr").N()

        $headers = @("Item")
        foreach ($h in $headers) {
            $ps.Append("th",$null,$h).N()
        }
        $ps.Up().N()

        ### add Itens of the Catalog to the Report
        [Array] $Items = $Catalog.ExtensionData.CatalogItems.CatalogItem

        foreach ($Item in $Items) {
            $ps.Add("tr").N()
            $ps.Append("td",$null,$Item.Name).N()

            $ps.Up().N()

        }

        $ps.Up().Up().N()
    }
    $ps.Up().N()

    ## add Org VDC`s to the Report
    $ps.Main().Append("h2",$null,"Org VDCs").N()

    foreach ($OrgVdc in $OrgVdcs) {
        $ps.Main().Add('table','table table-striped table-inverse').Add("tr").Append("th",$null,"VDC Name").Append("th",$null,"Enabled").Append("th",$null,"CpuUsedGHz").Append("th",$null,"MemoryUsedGB").Append("th",$null,"StorageUsedGB").Up().N()
        $ps.Add("tr").N()
        $ps.Append("td",$null,$OrgVdc.Name).Append("td",$null,$OrgVdc.Enabled).Append("td",$null,$OrgVdc.CpuUsedGHz).Append("td",$null,$OrgVdc.MemoryUsedGB).Append("td",$null,[Math]::Round($OrgVdc.StorageUsedGB,2)).Up().N()

        ### add Edge Gateways of this Org VDC to Report
        $ps.Main().Append("h3",$null,"Org VDC Edge Gateways").N()
        [Array] $Edges = Search-Cloud -QueryType EdgeGateway -Filter "Vdc==$($OrgVdc.Id)"

        foreach ($Edge in $Edges) {
            $ps.Add('table','table').Add("tr","bgtitle text-white").Append("th",$null,"Edge Name").N()
            $ps.Add("tr").N()
            $ps.Append("td",$null,$Edge.Name).Up().N()

            $ps.Add("td","bgsubsection").N()
            $ps.Add("table","table bgcolorsub").N()
            $ps.Append("tr").Append("td","font-weight-bold","HaStatus").Append("td",$null,($Edge.HaStatus)).N()
                $ps.Append("td","font-weight-bold","AdvancedNetworkingEnabled").Append("td",$null,$Edge.AdvancedNetworkingEnabled).N()
            $ps.Append("tr").Append("td","font-weight-bold","NumberOfExtNetworks").Append("td",$null,($Edge.NumberOfExtNetworks)).N()
                $ps.Append("td","font-weight-bold","NumberOfOrgNetworks").Append("td",$null,$Edge.NumberOfOrgNetworks).N()

            $ps.Up().Up().N()
        }
        $ps.Up().N()

        ### add Org Networks of this Org VDC to Report
        $ps.Main().Append("h3",$null,"Org VDC Networks").N()
        [Array] $Networks = $OrgVdc | Get-OrgVdcNetwork

        foreach ($Network in $Networks) {
            $ps.Add('table','table').Add("tr","bgtitle text-white").Append("th",$null,"Network Name").N()
            $ps.Add("tr").N()
            $ps.Append("td",$null,$Network.Name).Up().N()

            $ps.Add("td","bgsubsection").N()
            $ps.Add("table","table bgcolorsub").N()
            $ps.Append("tr").Append("td","font-weight-bold","DefaultGateway").Append("td",$null,($Network.DefaultGateway)).N()
                $ps.Append("td","font-weight-bold","Netmask").Append("td",$null,$Network.Netmask).N()
            $ps.Append("tr").Append("td","font-weight-bold","NetworkType").Append("td",$null,($Network.NetworkType)).N()
                $ps.Append("td","font-weight-bold","StaticIPPool").Append("td",$null,$Network.StaticIPPool).N()

            $ps.Up().Up().N()
        }
        $ps.Up().N()

        ### add vApps of this Org VDC to Report
        $ps.Main().Append("h3",$null,"Org VDC vApps").N()

        [Array] $Vapps = $OrgVdc | Get-CIVApp

        foreach ($Vapp in $Vapps) {
            $ps.Add('table','table').Add("tr","bgtitle text-white").Append("th",$null,"vApp Name").Append("th",$null,"Owner").Up().N()
            $ps.Add("tr").N()
            $ps.Append("td",$null,$Vapp.Name).Append("td",$null,$Vapp.Owner).Up().N()

            #### add VMs of this vApp to Report
            $ps.Add("td","bgsubsection").N()
            $ps.Add("table","table bgcolorsub").N()
            $ps.Add("tr").N()

            $headers = @("Name","Status","GuestOSFullName","CpuCount","MemoryGB")
            foreach ($h in $headers) {
                $ps.Append("th",$null,$h).N()
            }
            $ps.Up().N()

            [Array] $VMs = $Vapp | Get-CIVM

            foreach ($VM in $VMs) {
                $ps.Add("tr").N()
                $ps.Append("td",$null,$VM.Name).N()
                $ps.Append("td",$null,$VM.Status).N()
                $ps.Append("td",$null,$VM.GuestOSFullName).N()
                $ps.Append("td",$null,$VM.CpuCount).N()
                $ps.Append("td",$null,$VM.MemoryGB).N()

                $ps.Up().N()

            }
            $ps.Up().Up().N()

        }
        $ps.Up().N()

    }
    $ps.save($Path)

    "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") - Open HTML Report..."
    Start-Process $Path

}
}

The complete Module is also available as a GitHub repository.

Get the Module

I love automation, so I created a simple script to get the latest release of the vCloud Director Tenant HTML Report Module:

# Download
wget https://github.com/mycloudrevolution/VMware-vCD-TenantReport/archive/master.zip -OutFile C:\temp\master.zip

# Unzip
Add-Type -AssemblyName System.IO.Compression.FileSystem
function Unzip {
    param([string]$zipfile, [string]$outpath)
    [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath)
}

Unzip C:\temp\master.zip C:\temp\

# Import
Import-Module "C:\temp\VMware-vCD-TenantReport-master\VMware-vCD-TenantReport.psd1"

 

Leave a Reply