Chapter 15
Reporting and Auditing

In this chapter, you will learn to:

  • The Basics
  • Reporting 101
  • Techniques
  • Objects
  • Information Sources
  • PowerCLI Objects
  • vSphere View objects
  • ESXi Console Commands
  • Tasks and Events
  • Performance Data
  • CIM Interface
  • Other Sources
  • Report Formats
  • On the Screen
  • Files

Here we will show you how to use PowerShell and PowerCLI to create reports, where to get the data for your reports, and how to create reports in all kinds of formats. When you finish, be sure to take a look at Appendix A for example report scripts that you can use as a basis for your own automated reports.

The Basics

To get you started, let’s begin with some basic concepts. Besides a brief introduction to the PowerCLI Get cmdlets, which allow you to retrieve information, this section introduces some techniques for ordering and grouping the data.

Data in PowerShell is presented through objects. You will learn how you can create your own objects, and how to extend the objects returned by cmdlets.

Reporting 101

The basic building block that you will use for creating reports are the numerous Get cmdlets that are available in PowerCLI. A Get cmdlet usually returns one or more objects; each holds a number of properties that you can select for your report. Consider the next example:

Get-VM

Name                 PowerState Num CPUs MemoryGB       
----                 ---------- -------- --------       
VM3                  PoweredOff 1        2.000          
VM2                  PoweredOff 1        2.000          
VM1                  PoweredOff 1        4.000

The Get-VM cmdlet in this example returned three objects that each represent a virtual machine (VM). Remember that the PowerCLI cmdlets display only a limited set of the available properties for the objects.

With the Get-Member cmdlet you can see all the available properties on an object. For example:

Get-VM | Get-Member -MemberType Property | Select-Object -Property Name

Name
----
CDDrives
Client
CustomFields
DatastoreIdList
Description
DrsAutomationLevel
ExtensionData
FloppyDrives
Folder
FolderId
Guest
GuestId
HAIsolationResponse
HardDisks
...

Note that the last example only shows some of the available properties; there are many more.

Once you have determined which properties you want to include in your report, start composing your Select-Object line:

Get-VM | Select-Object -Property Name,PowerState,GuestId,VMHost |
    Format-Table -AutoSize


Name PowerState GuestId               VMHost            
---- ---------- -------               ------            
VM3  PoweredOff windows8_64Guest      esx61.local.test
VM2  PoweredOff windows8_64Guest      esx61.local.test
VM1  PoweredOff windows9Server64Guest esx62.local.test

In the example, we piped the result of the Select-Object cmdlet to the Format-Table cmdlet with the AutoSize switch. This makes the output more compact.

Although you can create great reports with the available properties on the objects returned by all the PowerCLI Get cmdlets, there are occasions when you want to display derived or more complex information in your report. That is where the calculated property is your friend. A calculated property is in fact a hash table with two elements: a name and an expression. In the expression element, you can provide a value for the property that you define in the name element.

The VirtualMachine objects that are by default returned by the Get-VM cmdlet include a property called ProvisionedSpaceGB, but since that property is defined as a decimal, it is displayed with a long series of values to the right of the decimal:

Get-VM | Select-Object -Property Name,PowerState,ProvisionedSpaceGB |
    Format-Table -AutoSize


Name PowerState             ProvisionedSpaceGB
---- ----------             ------------------
VM3  PoweredOff 34.140256862156093120574951172
VM2  PoweredOff 34.140256862156093120574951172
VM1  PoweredOff 44.140256862156093120574951172

In the next example, we use a .NET method from the System.Math class to round the decimal and display the data in a much more user-friendly fashion.

Get-VM | Select-Object Name,PowerState, 
    @{N='ProvSpaceGB';E={
      [Math]::Round($_.ProvisionedSpaceGB,1)
    }} |
    Format-Table -AutoSize


Name PowerState ProvSpaceGB
---- ---------- -----------
VM3  PoweredOff        34.1
VM2  PoweredOff        34.1
VM1  PoweredOff        44.1

In the next example, we used calculated properties to demonstrate how you can use calculated properties not only for formatting the result but also to create new properties:

Get-VMHost |
Select @{N='Name';E={$_.Name.Split('.')[0]}},
    @{N='MemUsage%';E={
        "{0:P1}" -f ($_.MemoryUsageMB/$_.MemoryTotalMB)
    }}
Name MemUsage%
---- ---------
esx1 18.1 %
esx2 34.0 %

In the previous example, we used the full names for all parts in the PowerShell code. But you can abbreviate several; PowerShell accepts a lot of abbreviations. Since PowerShell is intended for administrators, it foresees that typing lots of text is not your favorite pastime. You can use aliases (Select instead of Select-Object), abbreviated hash table element names (N instead of Name), and short notations for .NET classes ([math] instead of [System.Math]).

Techniques

In the previous section, we showed a very high-level view of how to create reports with the help of PowerCLI. In this section, we are going to give more techniques that are useful when creating reports.

Ordering the Data

PowerShell has some basic cmdlets that allow you to manipulate the data. One of these is the Sort-Object cmdlet. In the previous section, the returned objects were ordered as they were provided by PowerCLI. To make your reports more user friendly, you can impose a specific order on the returned objects.

Get-VM | Select Name,PowerState, 
    @{N='ProvSpaceGB';E={
      [Math]::Round($_.ProvisionedSpaceGB,1)
    }} |
    Sort-Object -Property Name |
    Format-Table -AutoSize


Name PowerState ProvSpaceGB
---- ---------- -----------
VM1  PoweredOff        44.1
VM2  PoweredOff        44.1
VM3  PoweredOff        17.1
VM4  PoweredOff        34.1

By simply piping the objects through the Sort-Object cmdlet, you can define the order of returned objects. The Sort-Object cmdlet allows the order to be imposed on any property and any sequence you like.

Get-VM | Select Name,PowerState, 
    @{N='ProvSpaceGB';E={
      [Math]::Round($_.ProvisionedSpaceGB,1)
    }} |
    Sort-Object -Property ProvSpaceGB -Descending |
    Format-Table -AutoSize


Name PowerState ProvSpaceGB
---- ---------- ------------------
VM2  PoweredOff               44.1
VM1  PoweredOff               44.1
VM4  PoweredOff               34.1
VM3  PoweredOff               17.1

You can also use multiple properties in your sort. If you want to have a different order for each of the properties, you will have to use a hash table.

Get-VM | Select Name,PowerState, 
    @{N='ProvSpaceGB';E={
      [Math]::Round($_.ProvisionedSpaceGB,1)
    }} |
    Sort-Object -Property @{Expression='ProvSpaceGB'; Descending=$True},
      @{Expression='Name'; Descending=$false} |
    Format-Table -AutoSize


Name PowerState ProvSpaceGB
---- ---------- -----------
VM1  PoweredOff        44.1
VM2  PoweredOff        44.1
VM4  PoweredOff        34.1
VM3  PoweredOff        17.1

Be careful when you are sorting on enumeration values. The string you see is not necessarily the internal integer value—the sort order returned could turn out to be something different than you expected.

Get-Cluster -Name cluster1 |
    Get-DrsRule |
    Sort-Object Type |
    Select Name,Type |
    Format-Table -AutoSize

Name                    Type
----                    ----
Anti-Affinity VMAntiAffinity
Affinity          VMAffinity

In this example, we wanted to sort on the DRS rule type, but alphabetically VMAntiAffinity does not come before VMAffinity. This occurs because the Type property is an enumeration type. Behind the user-friendly text, there are integer values, and it is those integer values the Sort-Object cmdlet uses. In the next script, we list the text and the value behind an enumeration, so you can more clearly see what Sort-Object does.

$rules = Get-Cluster -Name cluster1 | Get-DrsRule
$type = $rules[0].Type
[Enum]::GetValues($type.GetType()) | 
    Select @{N="UserFriendlyName";E={$_.ToString()}},
        @{N='InternalValue';E={$_.Value__}}


UserFriendlyName InternalValue
---------------- -------------
VMAntiAffinity               0
VMAffinity                   1
VMHostAffinity               2


[Enum]::GetValues($type.GetType()) |
    Select @{N="UserFriendlyName";E={$_.ToString()}}, value__

UserFriendlyName   value__
----------------   -------
VMAntiAffinity           0
VMAffinity               1
VMHostAffinity           2

But how can you sort in the alphabetical order? It turns out that alphabetical sorting is quite easy when you cast the Type property to a string on the Property parameter of the Sort-Object cmdlet:

Get-Cluster -Name cluster1 |
    Get-DrsRule |
    Sort-Object -Property {[string]$_.Type} |
    Select Name,Type |
    Format-Table -AutoSize

Name                    Type
----                    ----
Affinity          VMAffinity
Anti-Affinity VMAntiAffinity

Grouping the Data

To produce a report that groups specific entities together based on a property, you can use the Group-Object cmdlet. In its simplest form, you specify one property on which your objects will be grouped.

Get-VM | Group-Object -Property GuestId

Count Name                      Group
----- ----                      -----
    1 rhel7_64Guest             {VM6}
    1 sles12_64Guest            {VM3}
    1 windows7Server64Guest     {VM5}
    2 windows8Server64Guest     {VM2, VM1}
    1 windows7_64Guest          {VM4}

Notice that the objects produced by the Group-Object cmdlet are not the original objects as returned by the Get-VM cmdlet. The Group-Object cmdlet creates a new object, with specific properties like Count and Name. The actual objects that came out of the Get-VM cmdlet are all placed in an array under the Group property. This allows you to fetch individual properties from the objects in the group.

Get-VM |
    Group-Object -Property GuestId |
    Select Name,Count,
      @{N='VMHost';
        E={[string]::Join(',',($_.Group | Select -ExpandProperty VMHost))}} |
    Format-Table -AutoSize


Name                  Count VMHost                             
----                  ----- ------                             
rhel7_64Guest             1 esx61.local.test                 
sles12_64Guest            1 esx61.local.test                 
windows7Server64Guest     1 esx62.local.test                 
windows8Server64Guest     2 esx61.local.test,esx61.local.test
windows7_64Guest          1 esx61.local.test

Here we used a calculated property to list all the ESXi nodes on which the VirtualMachines in a specific group are located. Since the VMHost property for a group can have multiple ESXi nodes, the example uses the .NET Join method to convert the individual values into one string.

Objects

As you should be aware by now, the PowerCLI Get cmdlets produce objects (most of the time). These objects contain a number of properties that were selected by the PowerCLI development team. These properties were selected in such a way that, for most of the common use cases, required properties are present in the object. If you encounter a situation where this is not the case, don’t despair. There are many ways to extend objects with new properties.

Add-Member

To extend an existing object, you can use the Add-Member cmdlet. This cmdlet adds a user-defined property to an instance of a PowerShell object. Remember that Add-Member only adds the property to a specific object instance—and not to the object’s definition. Any new objects that you create won’t have the newly added property. If you want the property to be available in all new instances, you’ll need to modify the object’s definition in the Types.ps1xml file. This file, which is located in the PowerShell installation directory, is digitally signed to prevent tampering, but you can create your own Types.ps1xml file to further extend the types. Extending object definitions is beyond the scope of this book. If you want to modify the object types, start by looking at the PowerShell built-in about_Types.ps1xml Help topic:

Help about_Types.ps1xml

Now, let’s use the Add-Member cmdlet to add a numVM property to the cluster object that holds the number of virtual machines:

$clusterReport = @()
foreach ($cluster in Get-Cluster) {
  $cluster | Add-Member -MemberType NoteProperty -Name numVM '
      -Value @($cluster | Get-VM).count
  $clusterReport += $cluster
}
$clusterReport | Select Name, numVM

New-Object

When your reporting requirements call for information from different objects, it’s much easier to create your own custom object rather than extend an existing object. The best way to create a report like this is to define a custom object that includes all the properties you need in your report. A custom object can be created using the New-Object cmdlet and properties can be defined with the Add-Member cmdlet:

$myObject = New-Object Object
$myObject | Add-Member –MemberType NoteProperty '
    –Name Vm –Value $null
$myObject | Add-Member –MemberType NoteProperty '
    –Name HostName –Value $null
$myObject | Add-Member –MemberType NoteProperty '
    –Name ClusterName –Value $null

New-VIProperty

One of the features that introduced in PowerCLI 4.1 is the New-VIProperty cmdlet. This cmdlet lets you add your own properties to a specified PowerCLI object type. Because you are changing the object type (and not just a single existing instance like the Add-Member cmdlet does), the new property will be available on the next retrieval of the corresponding objects. Let’s illustrate this with an example.

Get-VM | Get-View | Select-Object Name, @{Name="ToolsVersion";
    Expression={$_.Config.Tools.ToolsVersion}}

Using that script, you are forced to use the Get-View cmdlet to access the underlying SDK object to retrieve the Tools version. Using the New-VIProperty cmdlet, you can create a new property to hold this information, so you don’t need to retrieve the underlying SDK anymore:

New-VIProperty -Name toolsVersion -ObjectType VirtualMachine '
    -ValueFromExtensionProperty 'Config.Tools.ToolsVersion'
Get-VM | Select Name, toolsVersion

You’ll notice that the code using the New-VIProperty cmdlet is much faster. This is because you don’t have to fetch the complete SDK object using the Get-View cmdlet.

Information Sources

Numerous sources of information are available to report on your vSphere environment. Some of these are easy to access; others will require a bit more effort. In this section, we will describe the major information sources that you can use in your reporting.

PowerCLI Objects

The PowerCLI objects are the objects returned by the PowerCLI cmdlets. As we stated earlier, these objects contain a set of properties that were selected by the PowerCLI development team. The selection of these properties was done in such a way that the most commonly used ones are directly available in the PowerCLI objects. To find out what is available, you can use the Get-Member cmdlet. It lists the available properties and property types for a PowerCLI object.

Get-VMHost -Name esx61.local.test | Get-Member -MemberType Property


   TypeName: VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl
Name                  MemberType Definition
----                  ---------- ----------
ApiVersion            Property   string ApiVersion {get;}
Build                 Property   string Build {get;}

Another useful method to investigate what is in the PowerCLI objects is through the use of the Format-Custom cmdlet. This cmdlet shows all nested properties, as far as you define on the Depth parameter. The cmdlet shows the values, if present, for each of the properties.

Get-Datastore -Name DS10 | Format-Custom -Depth 2
WARNING: The 'Accessible' property of Datastore type is deprecated
    Use the 'State' property instead
.
class VmfsDatastoreImpl
{
  FileSystemVersion = 5.61
  DatacenterId = Datacenter-datacenter-2
  Datacenter = 
    class DatacenterImpl
    {
      ParentFolderId = Folder-group-d1
      ParentFolder = 
        class FolderImpl
        {
          ParentId =

This can help if you are trying to find a property that holds a specific value. Just scroll through the generated output until you find the targeted value. The drawback is that the objects coming out of the Format-Custom cmdlet are intended for the output engine. It is not plain text that you could search with a -like or -match operator.

To help automate your searches of PowerCLI objects, you can use the ConvertTo-Text function and “dump” in text format to the specific depth the content of any object you feed it (see Listing 15-1). That way, you can easily search the PowerCLI object.

Listing 15-1: Dumping an object as text

function ConvertTo-Text {
<#
.SYNOPSIS
  Convert an object to text
.DESCRIPTION
  This function takes an object and converts it to a textual
  representation.
.NOTES
  Source:  Automating vSphere Administration
  Authors: Luc Dekens, Brian Graf, Arnim van Lieshout,
           Jonathan Medd, Alan Renouf, Glenn Sizemore,
           Andrew Sullivan
.PARAMETER InputObject
  The object to be represented in text format
.PARAMETER Depth
  Defines how 'deep' the function shall traverse the object.
  The default is a depth of 2.
.PARAMETER FullPath
  A switch that defines if the property is displayed with
  indentation or with the full path
.PARAMETER ExtensionData
  A switch that defines if the ExtensionData property shall
  be handled or not
.EXAMPLE
  Get-VM -Name VM1 | ConvertTo-Text -Depth 2
.EXAMPLE
  ConvertTo-Text -InputObject $esx -FullPath
#>

#Requires -Version 4.0

  [CmdletBinding()]
  Param(
    [Parameter(ValueFromPipeline=$True)]
    [object[]]$InputObject,
    [int]$Depth = 2,
    [switch]$FullPath = $false,
    [Switch]$ExtensionData = $false,
    [Parameter(DontShow)]
    [string]$Indent = '',
    [Parameter(DontShow)]
    [string]$Path = ''
  )

  Process{
    if($Indent.Length -lt $Depth){
      foreach($object in $InputObject){
        $object.PSObject.Properties | Foreach-Object -Process {
          if($FullPath){
            "$($Path + '' + $_.Name) - $($_.TypeNameOfValue) = $($_.Value)"
          }
          else{
            "$($Indent)$($_.Name) - $($_.TypeNameOfValue) = $($_.Value)"
          }
          if(($_.Name -ne 'ExtensionData' -or $ExtensionData) -and ' 
              $_.PSObject.Properties){
            $ctSplat = @{
              InputObject = $object."$($_.Name)"
              Depth = $Depth
              FullPath = $FullPath
              Path = "$($Path + '' + $_.Name)"
              Indent = " $($Indent)"
              ExtensionData = $ExtensionData
            }
            ConvertTo-Text @ctSplat
          }
        }
      }
    }  
  }
}

You can explore any PowerCLI object with this function, but be warned that the function might produce a long list of text. Some PowerCLI objects are quite complex and contain a lot of nested objects.

Since a PowerCLI object can now be dumped as text, you can use all the text search functionality that PowerShell offers. If you want to know, for example, where in a VirtualMachine object the MAC address of the NIC is stored, you can try the following:

$vm = Get-VM -Name vm1

$vm | ConvertTo-Text -Depth 2 -FullPath | 
    where {$_ -match '00:50:56:bc:7c:2c'}

NetworkAdaptersMacAddress - System.String = 00:50:56:bc:7c:2c

From the result, you find that the MAC address is stored under the NetworkAdapters property.

You do not always have to start at the root of a PowerCLI object; you can point to a nested property:

ConvertTo-Text -InputObject $esx.StorageInfo.ScsiLun -Depth 2 |
    where {$_ -match 'LunType'}

LunType - System.String = disk
LunType - System.String = disk
LunType - System.String = cdrom
LunType - System.String = disk

Based on that output, you can deduce that on this ESXi node three disk LUNs and one CD-ROM drive are connected.

vSphere View objects

The vSphere View objects are the objects that are used by vSphere internally. These objects are documented in the VMware vSphere API Reference, and will be discussed at greater length in Chapter 18, “The SDK.” vSphere View objects are presented to PowerCLI in two ways:

  • Under the ExtensionData property in several places in the PowerCLI objects
  • As objects returned by the Get-View cmdlet

In both cases, this representation of the vSphere View objects is a read-only copy of the actual vSphere object. This means that the content of the properties is not updated automatically. You will have to get the object again or use the UpdateViewData method.

$vm.ExtensionData.Config.Hardware.NumCPU
1

Set-VM -VM $vm -NumCpu 2 -Confirm:$false

Name                 PowerState Num CPUs MemoryGB       
----                 ---------- -------- --------       
VM1                  PoweredOff 2        4.000          



$vm.ExtensionData.Config.Hardware.NumCPU
1

$vm.ExtensionData.UpdateViewData()

$vm.ExtensionData.Config.Hardware.NumCPU
2

The ConvertTo-Text function from Listing 15-1 can also be used on vSphere View objects. The following short sample searches under the Config property of an ESXi node for the location where IPv6Enabled can be found.

ConvertTo-Text -InputObject $esx.ExtensionData.Config -Depth 2 |
    where {$_ -match 'IpV6Enabled'}
 IpV6Enabled - System.Nullable'1[[System.Boolean, mscorlib, Version=4.0.0.0,
Culture=neutral, P
ublicKeyToken=b77a5c561934e089]] = False
 AtBootIpV6Enabled - System.Nullable'1[[System.Boolean, mscorlib,
Version=4.0.0.0, Culture=neut
ral, PublicKeyToken=b77a5c561934e089]] = False

ESXi Console Commands

On the ESXi console there are many commands available to configure and display the ESXi settings. The esxcli command, which is by far the most important console command, can easily be accessed from within a PowerCLI session. Other console commands will require you to establish a Secure Shell (SSH) connection to the ESXi server.

Esxcli

With the Get-EsxCli cmdlet you have access to the functionality that is available in all the flavors of the esxcli command. The main difference is that you don’t need to SSH into the ESXi console.

To use the esxcli commands via PowerCLI, you first have to set up the esxcli object via the Get-EsxCli cmdlet.

$esx = Get-VMHost -Name esx1.local.test
$esxcli = Get-EsxCli -VMHost $esx

Once you have this object, you can navigate through all available namespaces and work with the available commands in each of the namespaces. In the next example, we are in the system.version namespace and use the get() method to obtain information regarding the ESXi version and build:

$esxcli.system.version.get()

Build   : Releasebuild-2494585
Patch   : 0
Product : VMware ESXi
Update  : 0
Version : 6.0.0

There are quite a few available namespaces, but for some of the methods in these namespaces, it is not obvious which parameters to use. To help with that, the Get-EsxCliCommand function in Listing 15-2 returns all the namespaces, the available methods, and the parameters for each method, and it includes Help text for each.

Listing 15-2: Returning details for each Get-EsxCli namespace

function Get-EsxCliCommand {
<#
.SYNOPSIS
  Returns all available namespaces through the Get-EsxCli object 
.DESCRIPTION
  The Get-EsxCli cmdlet returns an object that can be used to
  access all properties and methods that are available. This
  function retuns all available methods in these namespaces,
  together with a short help text and the method's parameters.
.NOTES
  Source:  Automating vSphere Administration
  Authors: Luc Dekens, Brian Graf, Arnim van Lieshout,
           Jonathan Medd, Alan Renouf, Glenn Sizemore,
           Andrew Sullivan
.PARAMETER VMHost
  The host for which to list the esxcli namespaces
.EXAMPLE
  Get-EsxCliCommand -VMHost $esx
#>
    [CmdLetBinding()]
    Param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [PSObject]$VMHost
    )

    Process{
        $esxcli = Get-EsxCli -VMHost $VMHost

        $esxcli.esxcli.command.list() | %{
            $steps = $_.NameSpace.Split('.') + $_.Command
            $stepsHelp = $_.NameSpace.Split('.') + 'help'
            
            # Get the namespace object
            $nSpace = $esxcli
            $steps | %{
                $nSpace = $($nSpace.$($_))
            }
            
            # Get the namespace Help object
            $helpObject = $esxcli
            $stepsHelp | %{
                $helpObject = $($helpObject.$($_))
            }
            
            # Fetch the help for the method
            if($helpObject){
                $helpObjectelp = $helpObject.Invoke($_.Command)
            }
            else{
                $helpObjectelp = $null
            }
            
            # Method details
            $_ | Select @{N='Command';E={"'$esxcli.$($_.NameSpace).$($_.Command)"}},
            @{N='Syntax/Parameter';E={"$($nSpace.Value.ToString())"}},
            @{N='Help';E={$helpObjectelp.Help}}
            
            # Parameter details
            if($helpObject){
                $helpObjectelp.Param | Select @{N='Command';E={''}},
                @{N='Syntax/Parameter';E={$_.DisplayName}},
                @{N='Help';E={$_.Help}}
            }
        }
    }
}

You call the function with a VMHost parameter, and you can redirect the returned information to a file or display it onscreen (see Figure 15-1).

Get-EsxCliCommand -VMHost $esxName | Out-GridView

This information should make it easier to use esxcli namespace methods. In Figure 15-1 you can see, for example, how the set method in the hardware.cpu.global namespace takes one parameter of type Boolean.

c15f001.tif

Figure 15-1: Get-EsxCliCommand sample output

Other Console Commands

When the Get-EsxCli namespaces do not provide access to the command or data you want to access, you can always fall back on establishing a SSH connection to the limited ESXi console based on BusyBox. To allow the SSH connection, you first have to make sure the SSH service is running on the ESXi node. This can be done with just a few cmdlets.

$esxNames = 'esx1.local.test','esx2.local.test'

Get-VMHost -Name $esxNames | Get-VMHostService |
    Where {$_.Key -eq "TSM-SSH"} |
    Start-VMHostService -Confirm:$false

Once the SSH service is running, you need to establish a SSH connection from your script to the ESXi console. One popular way of doing this is with the help of the plink.exe application that is part of the PuTTY Suite available from

www.chiark.greenend.org.uk/~sgtatham/putty/

This free collection of tools offers all kinds of functionality based on SSH.

The plink.exe application allows you to send a bash script to the ESXi node and capture the returned data.The following function (Listing 15-3) allows you, in a simple way, to use plink.exe to send commands to the ESXi console and get the output of that command in your script.

Listing 15-3: Running a command via SSH with plink.exe

function Invoke-EsxSSH {
<#
.SYNOPSIS
  Execute an ESXi console command through an SSH session
.DESCRIPTION
  This function is a wrapper for the plink.exe command in the
  PuTTY Suite.
.NOTES
  Source:  Automating vSphere Administration
  Authors: Luc Dekens, Brian Graf, Arnim van Lieshout,
           Jonathan Medd, Alan Renouf, Glenn Sizemore,
           Andrew Sullivan
.PARAMETER VMHost
  The host on which to execute a command
.PARAMETER Command
  The command to be executed
.PARAMETER Credential
  The credential to login to the host
.PARAMETER PathExe
  The path to the folder where the PuTTY Suite is stored
.PARAMETER IncludeError
  A switch to define if the returned output of the command 
  should include eventual error messages
.EXAMPLE
  PS> $esxsshSplat = @{
  VMHost   = $esx
  Command  = $cmd
  Credential = $cred
  PathExe  = 'C:PuTTY'
  }
  PS> Invoke-EsxSSH @esxsshSplat
.EXAMPLE
  Get-VMHost | Invoke-EsxSSH -Command $cmd -Credential $cred
#>

  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$VMHost,
    [Parameter(Mandatory=$true)]
    [String]$Command,
    [Parameter(Mandatory=$true)]
    [System.Management.Automation.PSCredential]$Credential,
    [String]$PathExe = 'C:Putty',
    [Switch]$IncludeError = $false
  )
  
  Begin{
    if(Test-Path -Path "$($PathExe)plink.exe"){
      $plink = "$($Path)plink.exe"
    }
    else{
      Throw "Application plink.exe not found in $($PathExe)"
    }
  }

  Process{
    ForEach($esx in $VMHost){
      if($esx -is [System.String]){
        $esx = Get-VMHost -Name $esx
      }
      $user = $Credential.UserName
      $password = $Credential.GetNetworkCredential().password
      $esxName = $esx.Name
      $plinkoptions = "-v -pw $password"
      $plink = "$($PathExe)plink.exe"
      $value = "rsa2@22:$($esx.Name)"
      $userInfo = $user,$esxName -join '@'
      $cmd = $plink,$plinkoptions,$userInfo,$Command,'2>&1' -join ' '
  
      Try{
        $branch = 'HKCU:SoftwareSimonTathamPuttySshHostKeys'
        Get-ItemProperty -Path $branch -Name $value -ErrorAction Stop | 
        Out-Null
      }
      Catch{
        $confirmation = 'Write-Output -InputObject "y" |'
        $cmd = $confirmation,$cmd -join ' '
      }

      $output = Invoke-Expression -Command $cmd

      if($IncludeError){
        $output
      }
      else{
        $output | 
        Where{$_ -isnot [System.Management.Automation.ErrorRecord]}
      }
    }
  }
}

The use of this function is quite straightforward, as the following example will show. In the example, we use the partedUtil command to get the partition layout of the system disk used by ESXi.

$esx = 'esx1.local.test'
$User = 'root'
$pswd = 'passoword'
$cmd = '/bin/partedUtil getptbl /vmfs/devices/disks/mpx.vmhba1:C0:T0:L0'
 
$secpswd = ConvertTo-SecureString $Pswd -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($User,$secpswd)
 
Invoke-EsxSSH -VMHost $esx -Command $cmd -Credential $cred -PathExe 'C:PuTTY'

The returned data will look something like this:

gpt
1044 255 63 16777216
1 64 8191 C12A7328F81F11D2BA4B00A0C93EC93B systemPartition 128
5 8224 520191 EBD0A0A2B9E5443387C068B6B72699C7 linuxNative 0
6 520224 1032191 EBD0A0A2B9E5443387C068B6B72699C7 linuxNative 0
7 1032224 1257471 9D27538040AD11DBBF97000C2911D1B8 vmkDiagnostic 0
8 1257504 1843199 EBD0A0A2B9E5443387C068B6B72699C7 linuxNative 0
9 1843200 7086079 9D27538040AD11DBBF97000C2911D1B8 vmkDiagnostic 0
2 7086080 15472639 EBD0A0A2B9E5443387C068B6B72699C7 linuxNative 0
3 15472640 16777182 AA31E02A400F11DB9590000C2911D1B8 vmfs 0

You can use the data to report on the partition layout and the disk space usage in each of these partitions. Further details on how this is done can be found in KB 1036609:

http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1036609

Tasks and Events

With tasks and events, you can report on all activity that takes place in your vSphere environment. The tasks and events data contains everything you see in the Tasks & Events tab in vSphere Client—and more. In all inventory views, vSphere Client offers two ways of looking at tasks and events. In Tasks view, you can view a list of all the tasks (upper pane), with the related events for each task (lower pane).

There are two sources of tasks and events available on vSphere servers:

  • On the ESXi nodes, where the data is stored for approximately one day
  • On the vCenter server, where the tasks and events data is kept for as long as you defined in your vCenter settings

To check what is currently configured on your vCenter, use the following:

Get-AdvancedSetting -Entity $global:DefaultVIServer |
    where {$_.Name -match '^task|^event' } |
    Select Name,Value

Name                   Value
----                   -----
event.maxAge           100
event.maxAgeEnabled    True
task.maxAge            100
task.maxAgeEnabled     False

On this specific vCenter the events are retained for 100 days, but the tasks are not retained. With the Set-AdvancedSetting cmdlet, you can change these settings. You can do something like this:

$tab = @{
  'event.maxAgeEnabled'=$true
  'event.maxAge'=365
  'task.maxAgeEnabled'=$true
  'task.maxAge'=365
}

$tab.GetEnumerator() | %{
  Get-AdvancedSetting -Entity $global:DefaultVIServer -Name $_.Name |
  Set-AdvancedSetting -Value $_.Value -Confirm:$false |
  Select Name,Value
}


Name                   Value
----                   -----
event.maxAge           365
event.maxAgeEnabled    True
task.maxAgeEnabled     True
task.maxAge            365

Notice how we used a hash table to define the Name and Value for the advanced settings.

The Get-Enumerator method that is available on the hash table will return each key-value pair that exists in the hash table. The key-value pairs are then passed, over the pipeline, to the Foreach-Object code block. In that code block, the AdvancedSetting, defined in the Name property, is updated set to the new value that was specified in the Value property.

The function in Listing 15-4 allows you to get an overview of all the events that are present in your vSphere environment. Note that the number of returned events depends on the components you have installed in your vSphere environment.

Listing 15-4: The Get-VIEventType function

function Get-VIEventType {
<#
.SYNOPSIS
  Returns all the available event types in the vSphere environment
  Can be used on against a vCenter and an ESXi server
.DESCRIPTION
  The function returns a string array that contains all the
  available event types in the current vSphere environment.
.NOTES
  Source:  Automating vSphere Administration
  Authors: Luc Dekens, Brian Graf, Arnim van Lieshout,
           Jonathan Medd, Alan Renouf, Glenn Sizemore,
           Andrew Sullivan
.PARAMETER Category
  Select the event type to report on.
  Default values are: info,warning,error,user
.EXAMPLE
  Get-VIEventType | Export-Csv -Path $csvName -NoTypeInformation
#>
  
  Param(
  [parameter(HelpMessage = "Accepted categories: info,warning,error,user")]
  [ValidateSet("info","warning","error","user")]
  [string[]]$Category = @("info","warning","error","user"))

  begin{
    $si = Get-View ServiceInstance
    $eventMgr = Get-View $si.Content.EventManager
  }

  process{
    $eventMgr.Description.EventInfo |
    Where-Object {$Category -contains $_.Category} | Foreach-Object {
      New-Object PSObject -Property @{
        Name = $_.Key
        Category = $_.Category
        Description = $_.Description
        Hierarchy = &{
          $obj = New-Object -TypeName ("VMware.Vim." + $_.Key)
          if($obj){
            $obj = $obj.GetType()
            $path = ""
            do{
              $path = ($obj.Name + "/") + $path
              $obj = $obj.BaseType
            } until($path -like "Event*")
            $path.TrimEnd("/")
          }
          else{
            "--undocumented--"
          }
        }
      }
    }
  }
}

The simplest use of the Get-VIEventType function returns all the events known in your vSphere environment. Notice how the Hierarchy property shows how each event was derived from the base class Event:

Get-VIEventType | fl


Name        : AccountCreatedEvent
Hierarchy   : Event/HostEvent/AccountCreatedEvent
Description : Account created
Category    : info

Name        : AccountRemovedEvent
Hierarchy   : Event/HostEvent/AccountRemovedEvent
Description : Account removed
Category    : info

Name        : AccountUpdatedEvent
Hierarchy   : Event/HostEvent/AccountUpdatedEvent
Description : Account updated
Category    : info

You will also find some undocumented, and hence unsupported, events in the list:

Name        : ChangeOwnerOfFileEvent
Hierarchy   : --undocumented--
Description : Change owner of file
Category    : info

Name        : ChangeOwnerOfFileFailedEvent
Hierarchy   : --undocumented--
Description : Cannot change owner of file name
Category    : error

And you will also encounter several of the special event types, ExtendedEvent and EventEx:

Name        : ExtendedEvent
Hierarchy   : Event/GeneralEvent/ExtendedEvent
Description : com.vmware.vcIntegrity.VMToolsNotRunning
Category    : error

Name        : EventEx
Hierarchy   : EventEx
Description : Lost Network Connectivity
Category    : error

There are a number of ways to retrieve the events for your vSphere environment.

The Get-VIEvent Cmdlet

This cmdlet retrieves the events from a vSphere server. This vSphere server can be a vCenter server or an ESXi host. See the Help topic for the details on how to use this cmdlet.

The SDK API

The following two examples illustrate how the SDK API can be used to enhance the retrieval of events.

The Get-Task cmdlet allows you to query tasks only in a specific state with the Status parameter. In some situations, it might be useful to be able to query tasks within a specific time frame. You might also want to find tasks that ran against specific entities. Listing 15-5 shows how you can retrieve tasks that ran against a specific entity using Get-VITaskSDK.

Listing 15-5: Retrieving tasks that ran against a specific entity

function Get-VITaskSDK {
<#
.SYNOPSIS
  Returns Tasks that comply with the specifications passed
  in the parameters
.DESCRIPTION
  The function will return vSphere tasks, as TaskInfo objects,
  that fit the specifications passed through the parameters.
  A connection to a vCenter is required!
.NOTES
  Source:  Automating vSphere Administration
  Authors: Luc Dekens, Brian Graf, Arnim van Lieshout,
           Jonathan Medd, Alan Renouf, Glenn Sizemore,
           Andrew Sullivan
.PARAMETER Entity
  The entity whose tasks shall be returned
.PARAMETER EntityChildren
  A switch that specifies if the tasks for the Entity or for
  the Entity and all its children shall be returned
.PARAMETER Start
  The beginning of the time range in which to look for tasks.
  If not specified, the function will start with the oldest
  available task.
.PARAMETER Finish
  The end of the time range in which to look for tasks.
  If not specified, the function will use the current time
  as the end of the time range.
.PARAMETER State
  The state of the tasks. Valid values are error, queued,
  running, success
.PARAMETER User
  If specified will only return tasks started by this user.
  If not specified the function will return tasks started by
  any user.
.EXAMPLE
  Get-Cluster -Name "MyCluster" | Get-VITaskSDK
.EXAMPLE
  Get-VITaskSDK -Entity (Get-VM myVM) -State "error"
#>

  Param(
      [parameter(Mandatory=$true,ValueFromPipeline = $true)]
      $Entity,
      [switch]$EntityChildren = $false,
      [DateTime]$Start,
      [DateTime]$Finish,
      [ValidateSet('error','queued','running','success')]
      [string]$State,
      [string]$User
  )

  Begin{
    if($defaultVIServer.ProductLine -ne "vpx"){
      Throw 'Error : you need to be connected to a vCenter'
    }

    $taskMgr = Get-View TaskManager
    $taskNumber = 100
  }

  Process{
    $Entity | Foreach-Object{
      $taskFilter = New-Object VMware.Vim.TaskFilterSpec
      $taskFilter.Entity = New-Object VMware.Vim.TaskFilterSpecByEntity
      $taskFilter.Entity.entity = $_.ExtensionData.MoRef
      if($EntityChildren){
        $taskFilter.Entity.recursion = 'all' 
      }
      else{
        $taskFilter.Entity.recursion = 'self'
      }
    }

    if($Start -or $Finish){
      $taskFilter.Time = '
        New-Object VMware.Vim.TaskFilterSpecByTime
      if($Start){
        $taskFilter.Time.beginTime = $Start
      }
      if($Finish){
        $taskFilter.Time.endTime = $Finish
        $taskFilter.Time.timeType = 'startedTime'
      }
    }

    if($State){
        $taskFilter.State = $State
    }

    if($User){
        $taskFilter.UserName = $User
    }

    $taskCollectorMoRef = $taskMgr.CreateCollectorForTasks($taskFilter)
    $taskCollector = Get-View $taskCollectorMoRef
    
    $taskCollector.RewindCollector | Out-Null

    $tasks = $taskCollector.ReadNextTasks($taskNumber)
    while($tasks){
      $tasks | Foreach-Object {
        $_
      }
      $tasks = $taskCollector.ReadNextTasks($taskNumber)
    }
    # By default 32 task collectors are allowed.
    # Destroy this task collector.
    $taskCollector.DestroyCollector()
  }
}

The Get-VIEvent cmdlet returns all events within a specific time frame. Sometimes, it could be handy to retrieve only events that were produced by a specific entity, eventually including all the entity’s children. The function in Listing 15-6 can be run against either a vCenter server or an ESXi server. When you run it against an ESXi server, it returns the last 1,000 events or all events since the last reboot of the server if there are fewer than 1,000 events.

Listing 15-6: Retrieving events produced by specific entities

function Get-VIEventSDK {
<#
.SYNOPSIS
  Returns Events that comply with the specifications passed
  in the parameters
.DESCRIPTION
  The function will return vSphere events, as Event objects,
  that fit the specifications passed through the parameters.
.NOTES
  Source:  Automating vSphere Administration
  Authors: Luc Dekens, Brian Graf, Arnim van Lieshout,
           Jonathan Medd, Alan Renouf, Glenn Sizemore,
           Andrew Sullivan
.PARAMETER Entity
  The entity whose events shall be returned
.PARAMETER EntityChildren
  A switch that specifies if the events for the Entity or for
  the Entity and all its children shall be returned
.PARAMETER Start
  The beginning of the time range in which to look for events.
  If not specified, the function will start with the oldest
  available event.
.PARAMETER Finish
  The end of the time range in which to look for events.
  If not specified, the function will use the current time
  as the end of the time range.
.PARAMETER EventChainId
  The function will only return events that have this specific
  EventChainId.
.PARAMETER User
  If specified will only return events for tasks triggered by
  this user. If not specified the function will return events
  independent of the user that started the task.
.EXAMPLE
  Get-VIEventSDK -Entity (Get-Cluster -Name "MyCluster")
.EXAMPLE
  Get-VM myVM | Get-VIEventSDK -EventChainId $task.EventChainId
#>

    Param(
    [parameter(Mandatory=$true,ValueFromPipeline = $true)]
    $Entity,
    [switch]$EntityChildren = $false,
    [DateTime]$Start,
    [DateTime]$Finish,
    [Int]$EventChainId,
    [String]$User
    )

    Begin{
        $si = Get-View ServiceInstance
        $eventMgr = Get-View $si.Content.EventManager
        $eventNumber = 100
    }

    Process{
        $Entity | ForEach-Object -Process {
            $eventFilter = New-Object VMware.Vim.EventFilterSpec
    
            if($Entity){
                $eventFilter.Entity = '
                  New-Object VMware.Vim.EventFilterSpecByEntity
                $eventFilter.Entity.entity = '
                  ($Entity | Get-View).MoRef
                if($EntityChildren){
                    $eventFilter.Entity.recursion = "all"
                }
                else{
                    $eventFilter.Entity.recursion = "self"
                }
            }
    
            if($Start -or $Finish){
                $eventFilter.Time = '
                  New-Object VMware.Vim.EventFilterSpecByTime
                if($Start){
                    $eventFilter.Time.beginTime = $Start
                }
                if($Finish){
                    $eventFilter.Time.endTime = $Finish
                    $eventFilter.Time.timeType = "startedTime"
                }
            }
    
            if($EventChainId){
                $eventFilter.eventChainId = $EventChainId
            }
    
            if($User){
                $taskFilter.UserName = $User
            }

            $eventCollectorMoRef = $eventMgr.CreateCollectorForEvents($eventFilter)
            $eventCollector = Get-View $eventCollectorMoRef

            $eventCollector.RewindCollector | Out-Null
    
            $events = $eventCollector.ReadNextEvents($eventNumber)
            while($events){
                $events | Foreach-Object {
                    $_
                }
                $events = '
                  $eventCollector.ReadNextEvents($eventNumber)
            }
            # By default 32 task collectors are allowed.
            # Destroy this task collector.
            $eventCollector.DestroyCollector()
        }
    }
}

The flow of these two functions is quite similar:

  1. Create a filter that contains all the specifications you passed via the function’s parameters.
  2. Create the collector.
  3. Use the collector in a while loop to retrieve all the objects that fit the specifications. Notice that these methods use a kind of sliding window that scrolls through all the tasks and events.
  4. Remove the collector. Remember that there can be only 32 tasks and 32 event collectors in existence.
  5. Exit the function.

Performance Data

A lot of information is available on the performance of all the components in your vSphere environment. How to access this performance data, and how to create reports from the data, is discussed in more detail in Chapter 16, “Using Statistical Data.”

CIM Interface

With PowerShell 3, a number of CIM cmdlets were introduced. These CIM cmdlets allow interaction with the Common Information Model (CIM), an open standard for querying managed objects in an IT environment. VMware uses CIM on the ESXi hypervisor to monitor the hardware on the server. This information is passed to the CIM Broker. You can see this information under Configuration - Health Status when connected to an ESXi server and under Hardware Status when connected to a vCenter.

The CIM classes provided by VMware are documented in the VMware CIM SMASH/Server Management API Reference:

http://pubs.vmware.com/vsphere-60/topic/com.vmware.sdk.doc/GUID-4406C028-AD55-4349-A6B8-09150B561438.html

Listing 15-7, based on a function published by Carter Shanklin former PowerCLI Product Manager, wraps a call to the ESXi CIM API.

Listing 15-7: CIM API wrapper

function Get-VMHostCimInstance {
<#
.SYNOPSIS
  Access the CIM interface of an ESXi node
.DESCRIPTION
  A wrapper function that calls the CIM interface of an ESXi
  node.
.NOTES
  Source:  Automating vSphere Administration
  Authors: Luc Dekens, Brian Graf, Arnim van Lieshout,
           Jonathan Medd, Alan Renouf, Glenn Sizemore,
           Andrew Sullivan
.PARAMETER HostName
  The hostname or IP address of the ESXi node
.PARAMETER Class
  The CIM class to query
.PARAMETER IgnoreCertFailures
  A switch to define if certificate errors shall be ignored
.PARAMETER Credential
  The credentials to login to the ESXi host.
.EXAMPLE
  PS> $cimSplat = @{
         HostName           = '192.168.1.1'
         Class              = 'VMware_PCIDevice'
         IgnoreCertFailures = $true
         Credential         = $cred
      }
  PS> Get-VMHostCimInstance @cimSplat
#>

    [CmdletBinding()]
    Param (
        [String]$HostName,
        [Parameter(ValueFromPipeline=$true)]
        [String]$Class,
        [Switch]$IgnoreCertFailures,
        [System.Management.Automation.PSCredential]$Credential
    )

    begin {
        Try {
            Get-Module -Name Cimcmdlets -ErrorAction Stop | Out-Null
        }
        Catch {
            Import-Module -Name CimCmdLets
        }

        $optCIMParams = @{
            Encoding = "Utf8"
            UseSsl = $true
            MaxEnvelopeSizeKB = 1024
        }
        if($IgnoreCertFailures){
            $optCIMParams.Add("SkipCACheck",$true)
            $optCIMParams.Add("SkipCNCheck",$true)
            $optCIMParams.Add("SkipRevocationCheck",$true)
        }
        $optCIM = New-CimSessionOption @optCIMParams

        $sessionCIMParams = @{
            Authentication = "Basic"
            Credential = $Credential
            ComputerName = $HostName
            Port = 443
            SessionOption = $optCIM
        }
        $session = New-CimSession @sessionCIMParams
    }

    process {
        $Class | Foreach-Object {
            $instanceCIMParams = @{
                CimSession = $session
            }
            if($_ -match "^CIM_"){
                $instanceCIMParams.Add("ClassName",$_)
            }
            if($_ -match "^OMC_"){
                $instanceCIMParams.Add("ResourceUri",
"http://schema.omc-project.org/wbem/wscim/1/cim-schema/2/$_")
            }
            if($_ -match "^VMware_"){
                $instanceCIMParams.Add("ResourceUri",
"http://schemas.vmware.com/wbem/wscim/1/cim-schema/2/$_")
            }
            Get-CimInstance @instanceCIMParams
        }
    }

    end {
        Remove-CimSession -CimSession $session 
    }
}

With this function, you can now easily query any CIM class and extract information. The next example queries for all PCI devices on an ESXi server:

$ipaddress = 'esx1.local.test'
$username = 'root'
$pswd = 'password'

$pswdSecure = ConvertTo-SecureString -String $pswd -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential '
        -ArgumentList $username,$pswdSecure

Get-VMHostCimInstance -Class 'VMware_PCIDevice' -HostName $ipaddress '
        -IgnoreCertFailures -Credential $cred |
Select DeviceID,
    @{N='VendorID';E={'0x{0:x4}' -f [int]$_.VendorID}},
    @{N='PCIDeviceID';E={'0x{0:x4}' -f [int]$_.PCIDeviceID}},
    @{N='Name';E={'{0,-15}' -f $_.ElementName}} |
Format-Table -AutoSize

The returned result looks something like this.

DeviceID     VendorID PCIDeviceID Name
--------     -------- ----------- ----
PCI 0:0:7:1  0x8086   0x7111      Intel Corporation PIIX4 for 430TX/… 
PCI 0:0:7:7  0x15ad   0x0740      VMware Virtual Machine Communicati…
PCI 0:0:15:0 0x15ad   0x0405      VMware SVGA II Adapter #15
PCI 0:0:16:0 0x1000   0x0030      LSI Logic / Symbios Logic 53c1030 …
PCI 0:2:0:0  0x8086   0x100f      Intel Corporation 82545EM Gigabit … 
PCI 0:2:1:0  0x8086   0x100f      Intel Corporation 82545EM Gigabit … 
PCI 0:2:2:0  0x8086   0x100f      Intel Corporation 82545EM Gigabit … 
PCI 0:2:3:0  0x8086   0x100f      Intel Corporation 82545EM Gigabit …

An attentive reader might have noticed that this ESXi node is obviously a nested ESXi. That explains the VMware PCI devices in the list.

Other Sources

You can, of course, use any kind of data in your reports, and numerous other sources are available that are directly or indirectly linked to your vSphere environment. Several of the vSphere family products come, for example, with a REST interface. With PowerShell and the Invoke-WebRequest cmdlet (make sure you are using at least PowerShell 4), it is easy to access REST interfaces.

Report Formats

Once you have collected the data for your report, you will need to present it in one form or another. With PowerShell, many formats for presenting your data are available.

On the Screen

This is the simplest way to visualize results from a script. As you have seen in previous examples in this chapter, when the Select-Object cmdlet is used and you do not redirect the output to another destination, the results will be displayed in the console of the PowerShell session.

Out-GridView

If you want to quickly view your report in a nice interactive table, you can use the Out-GridView cmdlet. Remember that this feature requires Microsoft .NET Framework 3.5 with Service Pack 1 to work. Using the interactive table, you can sort your data on any column by clicking the column header. To hide, show, or reorder columns, right-click a column header. You can also apply a search filter or define criteria by unfolding the Query window and adding criteria using the Add button. Figure 15-2 shows a typical Out-GridView report.

c15f002.tif

Figure 15-2: Sample Out-GridView report

Files

From within PowerShell you can create multiple file formats. The following sections show how to use some of the more commonly used file types.

Plain Text

Plain text is the simplest way to produce output from your scripts. In the previous examples in this chapter, several used a Select-Object to display the results. By default, this output will appear on the console of your PowerShell session.

Spreadsheet

You can export your report to a CSV file using the Export-Csv cmdlet. This way, the report can be easily imported into a spreadsheet program, like Microsoft Excel:

$report | Export-Csv c:	empMyReport.csv –NoTypeInformation

By default, the first line of the CSV file contains #TYPE followed by the fully qualified name of the type of the .NET Framework object. To omit the type information, use the -NoTypeInformation parameter.

You can also produce Excel spreadsheets from your PowerShell scripts. There are several functions around that produce XLSX files. One example is the Export-Xlsx function, which is published on LucD Notes here:

www.lucd.info/2013/01/03/export-xls-the-sequel-and-ordered-data/

Email

When you’re scheduling reports to run on a regular basis, it is always nice to receive the generated report in your inbox. From within PowerShell, you can send an email using the Send-MailMessage cmdlet. You can send a simple mail message that the report is finished; you can include the report as an attachment; or if you’ve created an HTML report, you can include it in the body of the mail message.

To email a fancy-looking HTML report to your manager:

Send-MailMessage -SmtpServer "[email protected]" '
  -From "[email protected]" '
  -To "[email protected]","[email protected]" '
  -Subject "My management report" '
  -Body $myHtmlReport –BodyAsHtml

To email some detailed reports as attachments to yourself:

Send-MailMessage -SmtpServer "[email protected]" '
  -From "[email protected]" '
  -To "[email protected]" '
  -Subject "My detailed reports" '
  -Body "Please review the attached detailed reports" '
  -Attachments "c:	emp
eport1.csv","c:	emp
eport2.csv"

Web Page

Although exporting to a CSV file is nice, if you want to do calculations, sorting, or filtering afterward, the format is not always well suited for a readable and distributable report. To create a more portable report, HTML is the preferred format. To create an HTML report, use the ConvertTo-Html cmdlet. Let’s use an example NIC report and convert it into an HTML page like the one shown in Figure 15-3:

$nicReport=@()
foreach ($cluster in Get-Cluster) {
  foreach ($vmHost in @($cluster | Get-VMHost)) {
    foreach ($nic in @($VMHost | Get-VMHostNetworkAdapter)) {
      $objNic = "" | Select ClusterName,HostName,Pci,DeviceName,Mac,BitRatePerSec,FullDuplex
      $objNic.ClusterName = $cluster.Name
      $objNic.HostName = $vmHost.Name
      $objNic.Pci = $nic.ExtensionData.Pci
      $objNic.DeviceName = $nic.DeviceName
      $objNic.Mac = $nic.Mac
      $objNic.BitRatePerSec = $nic.BitRatePerSec
      $objNic.FullDuplex = $nic.FullDuplex
      if ($nic.ExtensionData.Pci) {
        $nicReport += $ObjNic
      }
    }
  }
}
$nicReport | ConvertTo-Html > nicreport.html
c15f003.tif

Figure 15-3: Simple NIC HTML report

The HTML report shown in Figure 15-3 is still very basic, but there are ways to go beyond the ordinary. Let’s make it a bit fancier. The ConvertTo-Html cmdlet accepts several parameters that give you more flexibility in creating HTML output.

The -Body parameter is used to specify content that appears directly after the <body> tag and hence before the HTML table. This parameter is useful when you want to specify a header for your report:

$header = "<H2>Network Interface Card Report</H2>"

The -Head parameter is used to specify the content of the <head> tag. One very important thing that can be defined in the <head> section is style information for the HTML document using the <style> tag. Let’s create a custom <head> section and define some fancy HTML styles, like those shown in Figure 15-4:

$myStyle = @"
<title>My Fancy Html Nic Report</title>
<style>
body {background-color: coral;}
table {border-collapse: collapse; border-width: 1px;
    border-style: solid; border-color: black;}
tr {padding: 5px;}
th {border-width: 1px; border-style: solid; border-color: black;
    background-color: blue; color: white;}
td {border-width: 1px; border-style: solid; border-color: black;
    background-color: palegreen;}
</style>
"@

$nicReport |
    ConvertTo-Html -Body $header -Head $myStyle > nicreport.html
c15f004.tif

Figure 15-4: A fancy HTML report

The new report style shown in Figure 15-4 probably isn’t the best you’ve seen, but it gives you an idea of the possibilities. You’ll want your reports to use your company’s style. In that case, you can use the -CssUri parameter to include your company’s CSS style sheet:

$nicReport |
    ConvertTo-Html -CssUri c:mystylesheet.css > nicreport.html

If you don’t want to create a full HTML document but rather just an HTML table, use the -Fragment parameter. That way, you can build your own custom HTML report and include multiple objects into the same report. In Listing 15-8, you find an example of how to create your own custom HTML report using data from two different reports, $nicReport and $hbaReport, combined into one. As you can see, the only limitation is your own imagination.

Listing 15-8: A custom HTML report

$html = @"
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html><head>
<title>My Fancy Html Report</title>
<style>
body {background-color: coral;}
table {border-collapse: collapse; border-width: 1px;
    border-style: solid; border-color: black;}
tr {padding: 5px;}
th {border-width: 1px; border-style: solid; border-color: black;
    background-color: blue; color: white;}
td {border-width: 1px; border-style: solid; border-color: black;
    background-color: palegreen;}
</style>
</head><body>
"@

$html += "<h2>Network Interface Cards</h2>"
$html += $nicReport | ConvertTo-Html -Fragment

$html += "<h2>Host Bus Adapters</h2>"
$html += $hbaReport | ConvertTo-Html -Fragment

$html += @"
</body>
</html>
"@

$html > nicreport.html

A great example of an HTML report is produced by the vCheck script.

You can find additional reports in Appendix A in this book. In Appendix A we tried to compile a series of useful reports that cover multiple aspects of a vSphere environment.

With the knowledge you obtained in this chapter, you should be able to easily adapt the sample scripts for your environments.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset