Solved

How can I use ConvertTo-HTML in PowerShell to output values from multiple objects in a multi-column fashion?

Posted on 2015-02-09
5
375 Views
Last Modified: 2015-05-06
Folks -

I am trying to use PowerShell to create a very simple (or so I thought) report from VMM 2012 R2 that would help me hunt down VMs configured with Dynamic MAC address on their virtual NICs.  My enterprise uses static MACs across the board.

So, excluding all the wrapper code that would handle E-mailing the report to me, the main bulk of what gathers the info is as follows:

Import-Module VirtualMachineManager
Get-VMMServer myserver.mydomain.lcl

$VMs = Get-VM
foreach ($VM in $VMs)
{
	$VMNIC = $VM | Get-SCVirtualNetworkAdapter
	if ($VMNIC.MACAddressType -eq "Dynamic")
	{
		$VMNamesHTML += ConvertTo-HTML -InputObject $VM -Property Name -Fragment
		$VMNICsHTML += ConvertTo-HTML -InputObject $VMNIC -Property MACAddressType -Fragment

	}
}

ConvertTo-HTML -Body "$VMNameHTML $VMNICHTML" >>output.html

Open in new window


My problem with this solution is the output format.  The ConvertTo-HTML cmdlet lacks the ability to concatenate multiple variables into the body in multi-column form.  I'm looking for someone to help me get this information formatted in a more readable fashion.  Here is what I get from the code above:

Sample Output
For readability sake, the output should look more like this:

Name                               MACAddress
MYVIRTUALMACHINE    00:15:5D:7D:50:01

So in the case of there being multiple VMs and multiple MAC addresses, the information is easily readable without having to keep track of what line goes with what MAC address, as would be the case of everything is formatted in a single column.

This has become enormously frustrating for me.  Searching the net has resulted in massively divergent solutions to similar problems others have experienced.

ConvertTo-HTML has -InputObject and -Properties parameters.  Unfortunately for me, as is the case with most things PowerShell I run into, they're half-baked beyond belief.  -InputObject does not allow for multiple objects to be passed, as it should.  And therefore -Properties, which should be able to select properties from the multiple input objects by simply using an index number, does not.

Thank you in advance for any solutions you might dream up.
0
Comment
Question by:amendala
  • 2
  • 2
5 Comments
 
LVL 78

Expert Comment

by:David Johnson, CD, MVP
ID: 40599700
Something like the following would probably work.. NOT TESTED
Import-Module VirtualMachineManager
Get-VMMServer myserver.mydomain.lcl
$obj = new-object psobject
$VMs = Get-VM
foreach ($VM in $VMs)
{
	$VMNIC = $VM | Get-SCVirtualNetworkAdapter
	if ($VMNIC.MACAddressType -eq "Dynamic")
	{
		Add-Member -InputObject $obj -MemberType NoteProperty -Name "Machine" -Value $VM
        Add-Member -InputObject $obj -MemberType NoteProperty -Name "MAC-Address" -Value $VMNIC.MACAddressType
    }
}
$obj | ConvertTo-HTML -Body $obj > output.html
                                  

Open in new window

0
 
LVL 39

Accepted Solution

by:
footech earned 500 total points
ID: 40599802
I think there's a few reasons why ConvertTo-HTML doesn't take more than one input object.  I can't really think of any cmdlet that takes more than one (Compare-Object is the closest).  It's just not the way PS works.  If you think of any cmdlet that produces multiple columns (Format-Table, Export-CSV), you'll see it being fed by a stream of objects with the same properties.  The pipeline is one of the most important aspects of PowerShell, there's only one pipeline at a time.  Maybe it's a lack of imagination, but I can't imagine how things would work if you were trying to feed two (or more) pipelines into a single cmdlet.

In any case, the way to approach this is to input objects with the properties you desire into the ConvertTo-HTML cmdlet.  You'll see some variations of David's example, whether you're collecting the objects into an array and then feeding that array into ConvertTo-HTML, or utilizing the pipeline without collecting into an intermediate array.  The important thing is creating the object with the properties you desire (and there are a few techniques for doing that).  Here's one such variation.
Import-Module VirtualMachineManager
Get-VMMServer myserver.mydomain.lcl

$out = @()
$VMs = Get-VM
foreach ($VM in $VMs)
{
	$VMNIC = $VM | Get-SCVirtualNetworkAdapter
	if ($VMNIC.MACAddressType -eq "Dynamic")
	{
            $out += $VMNIC | Select @{n="Name";e={$VM.Name}},MACAddressType
	}
}
$out | ConvertTo-HTML | Out-File output.html

Open in new window

0
 

Author Comment

by:amendala
ID: 40601779
I had arrived at a partially working solution similar to David's on my own yesterday after much messing around but ran into a problem in that if there are multiple matches, you can't call Add-Member repeatedly because a member of the same name already exists and therefore you end up with a broken script.

So I created an array and in the loop, had a new object created and continued to iterate through the VMs and added the new object to the array.  I then piped that array to ConvertTo-HTML and what it produced was highly repetitive.  Each successive table included the objects before it.

$myArray = @()
$VMs = Get-VM
foreach ($VM in $VMs)
{
	$myObject = New-Object System.Object

	$VMNIC = $VM | Get-SCVirtualNetworkAdapter
	if ($VMNIC.MACAddressType -eq "Dynamic")
	{
		$myObject | Add-Member -type NoteProperty -name Name -value $VM.Name
		$myObject | Add-Member -type NoteProperty -name MACAddress -value $VMNIC.MACAddressType.ToString()

		$myArray += $myObject | Select Name, MACAddress
		$myArray | ConvertTo-HTML >>output.html
	}
}

Open in new window


I need the code to be able to handle multiple matches, create objects as they are encountered, add those objects to the array, and have ConvertTo-HTML render them out in a non-repetitive fashion.

What might I modify in the code above to fix that?
0
 

Author Comment

by:amendala
ID: 40601793
Interestingly enough footech, your code does work precisely as I'd prefer.  I tested it and the output is perfect.  Perhaps I should abandon my solution and look more at yours.  Though I am quite confused (and this is where my frustration with Powershell goes to the moon) at the syntax of:

$out += $VMNIC | Select @{n="Name";e={$VM.Name}},MACAddress

Everything else you coded makes perfect sense and is very elegant.  But that Select has me scratching my eyeballs out.  ;-)

The "@", "n", "e", etc. all make very little sense to me.  I'm rapidly picking up the syntactical quirks of PowerShell but for now, what that means entirely escapes me.  If you can give me a simple breakdown, I could flex this code to do a lot more for me.

Thanks in advance.
0
 
LVL 39

Expert Comment

by:footech
ID: 40601840
out += $VMNIC | Select @{n="Name";e={$VM.Name}},MACAddress
Here's another way to write the above
out += $VMNIC | Select @{name="Name";expression={$VM.Name}},MACAddress
#or
out += $VMNIC | % {  New-Object PsObject -properties @{  Name = $VM.Name; MACAddress = $_.MACAddress } }

Open in new window


Going back to the Select-Object command, the @{n="Name";e={$VM.Name}} part is known as a calculated property.  It is a hash table (hence the @{} notation).  The "n" is short for "Name" (you could also use "Label" or "l") and equals a string value which is the name of the property you are creating (can be anything you want).  "e" is short for "expression" and equals a scriptblock (hence the curly brackets) that evaluates to the desired value.
0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

How to sign a powershell script so you can prevent tampering, and only allow users to run authorised Powershell scripts
"Migrate" an SMTP relay receive connector to a new server using info from an old server.
This tutorial will walk an individual through the process of transferring the five major, necessary Active Directory Roles, commonly referred to as the FSMO roles from a Windows Server 2008 domain controller to a Windows Server 2012 domain controlle…
This tutorial will walk an individual through the process of configuring their Windows Server 2012 domain controller to synchronize its time with a trusted, external resource. Use Google, Bing, or other preferred search engine to locate trusted NTP …

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now