ConvertTo-Html, issue with piping $Myvar

Updated: 20241221@2045
Before offering\providing an alternative method, please point out what have I broken below.

gc "D:\Computers\_01\Comp_Info.txt"

… outputs the correct computerinfo properties, in a formatted table.

gc "D:\Computers\_01\Comp_Info.txt" | ConvertTo-Html | Out-File D:\Computers\_01\aliases.htm

… results in:

PSPath	PSParentPath	PSChildName	PSDrive	PSProvider	ReadCount	Length
D:\Computers\_01\Comp_Info.txt	D:\Computers\_01	Comp_Info.txt	D	Microsoft.PowerShell.Core\FileSystem	1	46
D:\Computers\_01\Comp_Info.txt	D:\Computers\_01	Comp_Info.txt	D	Microsoft.PowerShell.Core\FileSystem	2	40
D:\Computers\_01\Comp_Info.txt	D:\Computers\_01	Comp_Info.txt	D	Microsoft.PowerShell.Core\FileSystem	3	39
D:\Computers\_01\Comp_Info.txt	D:\Computers\_01	Comp_Info.txt	D	Microsoft.PowerShell.Core\FileSystem	4	37
D:\Computers\_01\Comp_Info.txt	D:\Computers\_01	Comp_Info.txt	D	Microsoft.PowerShell.Core\FileSystem	5	36
D:\Computers\_01\Comp_Info.txt	D:\Computers\_01	Comp_Info.txt	D	Microsoft.PowerShell.Core\FileSystem	6	39
D:\Computers\_01\Comp_Info.txt	D:\Computers\_01	Comp_Info.txt	D	Microsoft.PowerShell.Core\FileSystem	7	28
D:\Computers\_01\Comp_Info.txt	D:\Computers\_01	Comp_Info.txt	D	Microsoft.PowerShell.Core\FileSystem	8	34
D:\Computers\_01\Comp_Info.txt	D:\Computers\_01	Comp_Info.txt	D	Microsoft.PowerShell.Core\FileSystem	9	44

What am I not understanding about this?

PSVersion 5.1.22621.4249

what are the contents of Comp_Info.txt? Or at least a sample

@grey0ut – apologies for not including a sample. The contents of ‘Comp_Info.txt’ is a combination of:

Get-ComputerInfo OsLastBootUpTime, Osuptime, csmodel, biosseralnumber, BiosSMBIOSBIOSVersion, "WindowsProductName", "OSDisplayVersion", "OsVersion", osinstalldate > D:\Computers\Scripts\PowerShell\Learn_PowerShell_in_a_Month_of_Lunches_\TEMP\Comp_Info.txt
#
Get-WindowsDriver -online -all | ? -property providername -like *real* | select-object driver, version, ProviderName, date | sort date -Descending >> D:\Computers\Scripts\PowerShell\Learn_PowerShell_in_a_Month_of_Lunches_\TEMP\Comp_Info.txt
#
(gc D:\Computers\Scripts\PowerShell\Learn_PowerShell_in_a_Month_of_Lunches_\TEMP\Comp_Info.txt) | ? {$_.trim() -ne "" } > D:\Computers\Scripts\PowerShell\Learn_PowerShell_in_a_Month_of_Lunches_\TEMP\Comp_Info.txt

Grey0ut, I’m sure you can read the above, allow me to suggest --change “real”, as you see fit, then run the .ps1 on your machine; I continue to listen.

You’re outputting just text. When you read it back in it’s still just text. You will want to output in a structured manner such as csv, json, xml, etc. Then when you read it back in you will have objects that you can actually work with.

1 Like

@krzydoug – Thank you, once again I have confused myself. The incorrect thinking was --because the data was read into the Comp_Info.txt from an object, when any command retrieved the content from Comp_Info.txt, it would remain or be processed as an object.

While I play/implement your resolve, please provide a link that explains this process. By that I mean, when I redirected the output to Comp_Info.txt, is there an article which details that process? How it gets stripped of all it’s identifiable object properties, then ends up as text in such a way that --it looks the same as the command output to the Console, but it’s no longer an object.

Continued Respect

I like to work through these things a piece at a time so if something doesn’t work the way I expect I can narrow in on where and why.
Here is the code you shared with us:

Get-ComputerInfo OsLastBootUpTime, Osuptime, csmodel, biosseralnumber, BiosSMBIOSBIOSVersion, "WindowsProductName", "OSDisplayVersion", "OsVersion", osinstalldate > D:\Computers\Scripts\PowerShell\Learn_PowerShell_in_a_Month_of_Lunches_\TEMP\Comp_Info.txt
#
Get-WindowsDriver -online -all | ? -property providername -like *real* | select-object driver, version, ProviderName, date | sort date -Descending >> D:\Computers\Scripts\PowerShell\Learn_PowerShell_in_a_Month_of_Lunches_\TEMP\Comp_Info.txt
#
(gc D:\Computers\Scripts\PowerShell\Learn_PowerShell_in_a_Month_of_Lunches_\TEMP\Comp_Info.txt) | ? {$_.trim() -ne "" } > D:\Computers\Scripts\PowerShell\Learn_PowerShell_in_a_Month_of_Lunches_\TEMP\Comp_Info.txt

Let’s focus on the first line and use it as an example. We’re calling Get-ComputerInfo with an array of properties. Though we didn’t specify the -Property paramater, it is the parameter we’re providing value to based on position. To make it a little more readable we could also define all of those properties in an array, oriented vertically, and then specify that array at execution.


$Properties = @(
    "OsLastBootUpTime",
    "Osuptime",
    "csmodel",
    "biosseralnumber",
    "BiosSMBIOSBIOSVersion",
    "WindowsProductName",
    "OSDisplayVersion",
    "OsVersion",
    "osinstalldate"
)

Get-ComputerInfo -Property $Properties | Out-File C:\Scripts\Testing\Comp_Info.txt

One caveat here is that when defining the array all of those property names need to be quoted to be treated as strings. Whereas when providing them at execution (e.g. Get-ComputerInfo OSuptime, csmodle) you can get away without the quotes.

Notice that I piped the output to Out-File to write the text to a file? Your example uses the redirection operator, which is fine, but I’m going to stay with piping to powershell cmdlets.

What does this command output look like?


And if you redirect, or pipe it to out-file, and write to a file?

It looks the exact same. Cool, but important, because if you read that content back in to powershell with Get-Content it will treat it as lines of text because that’s all Get-Content does. There is no magic built in to Get-Content to try to determine if the text it’s reading could be converted to any other Powershell object other than a String object.
Let’s look

I read the content of the text file in to a variable $Content. If I just call the variable $Content the console output will look identical to the original Get-ComputerInfo cmdlet:

But it’s technically different, because they are different object(s).
The original Get-ComputerInfo output is a PSCustomObject:

All of this is important to your end goal because ConvertTo-Html has a parameter called -InputObject that accepts the object you want to render in HTML. The cmdlet will take the property names off the input object and use them to make the HTML tags. If you’re using Get-Content to feed objects to ConverTo-Html then you will only get properties of String objects because as we can see from the Get-Content documentation it outputs string objects.

Looking at your other data collection line, the Get-WindowsDriver one, we can capture the output and observe the type of objects that are being output:

If we take just the driver objects, convert them to HTML, and then write them to a file:

$Drivers = Get-WindowsDriver -online -all | ? -property providername -like *real* | select-object driver, version, ProviderName, date | sort date -Descending
$Drivers | ConvertTo-Html | out-file c:\Scripts\Testing\Drivers.html

We get a webpage that looks like a good representation of those objects

Having no experience with ConverTo-Html, I don’t feel like figuring out how to combine the disparate output objects of Get-ComputerInfo and Get-WindowsDriver in to a single HTML page. You may want to explore the other parameters that ConverTo-Html has in regards to creating the head, title, body etc.

Technically, Powershell is processing the content as an object. But a string object. Remember, everything in Powershell is objects. Whether it’s the object type you’re expecting is another factor.

This is something that will become more “second nature” as you gain experience in Powershell. Whether you use the redirect operator > or pipe to Out-File their output type is more or less trying to preserve the appearance of the console output, with no regard for the object type. See notes from Out-File’s documentation.

Instead, experiment with using cmdltes like Export-Csv and Export-CliXml. I use Export-Csv a lot. Properties of objects are turned in to the header row in the CSV, and property values run as rows. Example:

$CompInfo | Export-Csv C:\Scripts\Testing\Comp_Info.csv

Will result in a CSV like this:


If you want to have that first row omitted at export add the parameter -NoTypeInformation or the shorthand -nti.

$CompInfo | Export-Csv C:\Scripts\Testing\Comp_Info.csv -nti

But bear in mind that this works with like objects. If you try appending objects with different properties to that same CSV you’ll run in to problems.

2 Likes

@grey0ut – This will keep me busy for a minute, there is quite a bit in your response that I have not yet read about in the 4th Edition. Within your reply, at first glance I fully understood:

$Drivers = Get-WindowsDriver -online -all | ? -property providername -like *real* | select-object driver, version, ProviderName, date | sort date -Descending
$Drivers | ConvertTo-Html | out-file c:\Scripts\Testing\Drivers.html

As I understand the above, you place the output --as an object into $Drivers, where it remains an object; not a string. Your second line pipes the contents of $Drivers, as an object, to convertto-html, inturn to outfile

Please confirm if I understood\explained correctly. If so, the behavior of your first line, is what I was expecting when the initial code I posted that generated the txt. I had no clue it was turning the object into a string and would never be recognized as an object ever again. I will have to slow-down and pay closer attention to what I’m reading and run numerous test until I understand these speed-bumps, although as you stated "This is something that will become more “second nature” as you gain experience in Powershell. "

Thank you

This is the thing with Powershell: everything is objects since it’s an object oriented language. A string is an object. The original output from Get-WindowsDriver is a different type of object. Piping to Select-Object effectively converts the objects to PSObjects (see Select-Object documentation).
We could have piped Get-WindowsDriver to Where-Object, to Select-Object, to Sort-Object, to ConvertTo-Html to Out-File, but that is very hard to read and doesn’t give you a chance to play with any of the objects in between.
I used the $Drivers variable so that I could manually play around with it, explore its contents and object types, pipe it to other cmdlets etc and see how it behaved. I did not execute a .ps1 file once. Instead I had this code in VS Code and was selectively executing the lines, or using the built-in terminal to call the variables.

@grey0ut – above is exactly where I was confused, I.E. thinking a ‘string’ object, was recognized the same as it was when the cmdlet retrieved it. I hope I said that correctly.

I intend to do just (above), this is a major speed-bump for me.

I did initially try your method, however I was using the ‘string’ as a object, which broke my world; until you screwed my head on straight.

I’m limiting myself to the default Win-11\PS 5.1 terminal install until I get a firm grasp on the help-system. I haven’t yet played with ISE, albeit easier. just the saying\typing ‘VS Code’ generates a cerebral aneurysm. I’ll get to it at some point.

The data you have provided in this post is most helpful for me, I have not yet gotten this far in the book.

Thank you

As others will likely echo: do not bother with ISE. It’s been abandoned by Microsoft and is only going to make your life harder.
Visual Studio Code is free and I use it on Windows and Linux. There’s no reason not to use it.

If you’re not writing script/code in some kind of IDE you’re also hamstringing yourself. I use Powershell as a CLI a lot but I also write a lot of code in IDEs like VS Code and it’s much easier to write multi-line code in an IDE

1 Like