Last few weeks, I've been working on making creating HTML based Dashboards, Reports, and Emails better. PSWriteHTML already allows fancy looking reports or emails without much effort, but this release makes it even more helpful. I will be mixing three PowerShell modules in this blost post – PSWriteHTML (responsible for creating HTML/CSS/JS code), Emailimo (simplifies creating emails based on PSWriteHTML) and Dashimo (simple dashboard building). If you've never heard of those modules before I encourage you to start from earlier blogs about them to understand the concepts before you dive into this one. Hopefully, those will give you some ideas that will match what you will learn today.
Also, this post means that following PowerShell module was updated in PSGallery. Before updating them on production, please test whether your code still works as required because I've done a lot of changes (even thou those shouldn't be visible) preparing for new features.
As always code on PowerShellGallery is optimized for speed, while the one stored on GitHub for development.
While I've made plenty of changes and added a couple of new features, I want to focus on Tables again. I know it was already a focus for me in earlier releases, but I was missing a crucial element. Conditional formatting and styling of content inline. While Conditional Formatting was already part of previous versions, it was based on JavaScript. That means conditional formatting happens while you open up your browser. Thanks to this there's almost no time spent on verification whether something matches condition or not while it's being generated in PowerShell. The drawback is it doesn't work when JavaScript is disabled, which is something widespread for emails. This release adds the ability to style content based on column and rows or use conditional formatting to apply style but make it permanent in HTML. Confusing? Let's take a look at a reasonably simple table of Domain Controllers in my test Active Directory Forest.
Creating this table in HTML is not trivial if you would like to do it manually. There are multiple things in play here. First of all, we have headers that span across two rows. We have headers that span across multiple columns. We also have green, yellow, and red colors in different parts of content to make data in them more accessible to spot on first look. While manually it would be hard to create with PSWriteHTML (and for that matter in Emailimo or Dashimo) it's quite easy if you understand the concepts. Since Table above is a screenshot from Email, I will be using Emailimo to describe features you see above.
$DomainControllers = Get-WinADDomainControllers -TestAvailability EmailTable -DataTable $DomainControllers { EmailTableHeader -Names 'IPV4Address', 'IPV6Address' -Title 'Ip Addresses' -Alignment center -Color White -BackGroundColor Gray EmailTableHeader -Names 'SchemaMaster', 'PDCEmulator', 'RIDMaster', 'DomainNamingMasterMaster', 'InfrastructureMaster' -Title 'Roles' -Alignment center -Color White -BackGroundColor Gray EmailTableHeader -Names 'LdapPort', 'SslPort' -Title 'Ports' -Alignment center -Color White -BackGroundColor Gray EmailTableCondition -ComparisonType 'string' -Name 'SchemaMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'PDCEmulator' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'RIDMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'IsReadOnly' -Operator eq -Value 'True' -BackgroundColor Yellow -Color Black -Inline EmailTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator ne -Value 'True' -BackgroundColor Red -Color White -Inline } -HideFooter
In code above, you see four functions: Get-WinADDomainControllers, which is getting me all my Domain Controllers within a forest (not part of today's blog). Then as you can see, I'm using three functions from Emailimo module – EmailTable, EmailTableHeader, and EmailTableCondition. First, you create a table with EmailTable; you open bracket to tell Emailimo that EmailTableHeader and EmailTableConditions function will apply to that table and nothing else and then you define their action. As you can see above, I've used three EmailTableHeader functions. I wanted to add a description and somehow connect all FSMO roles under one header called Roles. So I defined all Column Names and gave it a Title.
EmailTableHeader -Names 'SchemaMaster', 'PDCEmulator', 'RIDMaster', 'DomainNamingMasterMaster', 'InfrastructureMaster' -Title 'Roles' -Alignment center -Color White -BackGroundColor Gray
In the next case, I've two columns called LdapPort and SSLPort that I wanted to be visible under one name Ports. With Emailimo it's as simple as one command
EmailTableHeader -Names 'LdapPort', 'SslPort' -Title 'Ports' -Alignment center -Color White -BackGroundColor Gray
Of course, as you may have already noticed, I also gave it some BackgroundColor some Color for text and some alignment. But there are other styling options available. But you already knew that from my earlier blog, right? But here's the critical bit EmailTableCondition. In this case, I've defined five conditions. I wanted to color code all Domain Controllers where the FSMO roles are. I also wanted to color code computers which are pingable and mark those which aren't with red color. This was already covered in my blog post about Dashimo. What wasn't included is the switch Inline. That little switch is critical if you want the styling to work with JavaScript disabled (hence why I show this in Emailimo).
EmailTableCondition -ComparisonType 'string' -Name 'SchemaMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'PDCEmulator' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'RIDMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'IsReadOnly' -Operator eq -Value 'True' -BackgroundColor Yellow -Color Black -Inline EmailTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator ne -Value 'True' -BackgroundColor Red -Color White -Inline
How does it look in HTML?
<table id="DT-wmronOCJ" class="display compact"> <thead> <tr> <th rowspan="2">Domain</th> <th rowspan="2">HostName</th> <th rowspan="2">Forest</th> <th style="color:#ffffff;background-color:#808080;text-align:center" colspan="2">Ip Addresses</th> <th rowspan="2">IsGlobalCatalog</th> <th rowspan="2">IsReadOnly</th> <th rowspan="2">Site</th> <th style="color:#ffffff;background-color:#808080;text-align:center" colspan="5">Roles</th> <th style="color:#ffffff;background-color:#808080;text-align:center" colspan="2">Ports</th> <th rowspan="2">Comment</th> <th rowspan="2">Pingable</th> </tr> <tr> <th>IPV4Address</th> <th>IPV6Address</th> <th>SchemaMaster</th> <th>DomainNamingMasterMaster</th> <th>PDCEmulator</th> <th>RIDMaster</th> <th>InfrastructureMaster</th> <th>LdapPort</th> <th>SslPort</th> </tr> </thead> <tbody> <tr> <td>ad.evotec.xyz</td> <td>AD2.ad.evotec.xyz</td> <td>ad.evotec.xyz</td> <td>192.168.240.192</td> <td></td> <td>True</td> <td>False</td> <td>KATOWICE-1</td> <td>False</td> <td>False</td> <td>False</td> <td>False</td> <td>False</td> <td>389</td> <td>636</td> <td></td> <td style="color:#ffffff;background-color:#008000">True</td> </tr> <tr> <td>ad.evotec.xyz</td> <td>AD1.ad.evotec.xyz</td> <td>ad.evotec.xyz</td> <td>192.168.240.189</td> <td></td> <td>True</td> <td>False</td> <td>KATOWICE-1</td> <td style="color:#ffffff;background-color:#008000">True</td> <td>True</td> <td style="color:#ffffff;background-color:#008000">True</td> <td style="color:#ffffff;background-color:#008000">True</td> <td>True</td> <td>389</td> <td>636</td> <td></td> <td style="color:#ffffff;background-color:#008000">True</td> </tr> <tr> <td>ad.evotec.xyz</td> <td>AD3.ad.evotec.xyz</td> <td>ad.evotec.xyz</td> <td>192.168.240.236</td> <td></td> <td>True</td> <td>False</td> <td>KATOWICE-2</td> <td>False</td> <td>False</td> <td>False</td> <td>False</td> <td>False</td> <td>389</td> <td>636</td> <td></td> <td style="color:#ffffff;background-color:#008000">True</td> </tr> <tr> <td>ad.evotec.pl</td> <td>ADPreview2019.ad.evotec.pl</td> <td>ad.evotec.xyz</td> <td>192.168.240.201</td> <td></td> <td>True</td> <td>False</td> <td>KATOWICE-2</td> <td>False</td> <td>False</td> <td>False</td> <td>False</td> <td>False</td> <td>389</td> <td>636</td> <td></td> <td style="color:#ffffff;background-color:#008000">True</td> </tr> <tr> <td>ad.evotec.pl</td> <td>DC1.ad.evotec.pl</td> <td>ad.evotec.xyz</td> <td>192.168.240.238</td> <td></td> <td>True</td> <td>False</td> <td>KATOWICE-1</td> <td>False</td> <td>False</td> <td style="color:#ffffff;background-color:#008000">True</td> <td style="color:#ffffff;background-color:#008000">True</td> <td>True</td> <td>389</td> <td>636</td> <td></td> <td style="color:#ffffff;background-color:#008000">True</td> </tr> <tr> <td>ad.evotec.pl</td> <td>ADRODC.ad.evotec.pl</td> <td>ad.evotec.xyz</td> <td>192.168.240.207</td> <td></td> <td>True</td> <td style="color:#000000;background-color:#ffff00">True</td> <td>GLIWICE</td> <td>False</td> <td>False</td> <td>False</td> <td>False</td> <td>False</td> <td>389</td> <td>636</td> <td></td> <td style="color:#ffffff;background-color:#ff0000">False</td> </tr> </tbody> </table>
What this Inline switch did is apply style for each Table Cell separately as required. It does this for headers with row spanning and column spanning and does this for styling. For this feature to work, it means that I have to rebuild table on the fly in PowerShell rather than relying on JavaScript at runtime. That means you may see some speed difference when using EmailTableCondition (Emailimo)/TableCondition (Dashimo)/New-HTMLTableCondition (PSWriteHTML) with and without Inline switch. Full code to design and send email is as below.
Import-Module Emailimo -Force $DomainControllers = Get-WinADDomainControllers -TestAvailability Email { EmailHeader { EmailFrom -Address '[email protected]' EmailTo -Addresses "[email protected]" EmailServer -Server 'smtp.office365.com' -UserName 'rpassword' -Password 'C:\Support\Important\Password-Evotec-Reminder.txt' -PasswordAsSecure -PasswordFromFile EmailOptions -Priority High -DeliveryNotifications Never EmailSubject -Subject 'This is a test email' } EmailBody -FontFamily 'Calibri' -Size 15 { EmailText -Text "Domain Controllers ", 'Status' -Color Blue, None EmailTable -DataTable $DomainControllers { EmailTableHeader -Names 'IPV4Address', 'IPV6Address' -Title 'Ip Addresses' -Alignment center -Color White -BackGroundColor Gray EmailTableHeader -Names 'SchemaMaster', 'PDCEmulator', 'RIDMaster', 'DomainNamingMasterMaster', 'InfrastructureMaster' -Title 'Roles' -Alignment center -Color White -BackGroundColor Gray EmailTableHeader -Names 'LdapPort', 'SslPort' -Title 'Ports' -Alignment center -Color White -BackGroundColor Gray EmailTableCondition -ComparisonType 'string' -Name 'SchemaMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'PDCEmulator' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'RIDMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'IsReadOnly' -Operator eq -Value 'True' -BackgroundColor Yellow -Color Black -Inline EmailTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator ne -Value 'True' -BackgroundColor Red -Color White -Inline } -HideFooter EmailText -LineBreak EmailTextBox { 'Kind regards,' 'Evotec IT' } } }
In just 35 lines, I was able to produce a nicely formatted email with readable code. Something that wasn't possible before.
As mentioned above, Emailimo, Dashimo, and PSWriteHTML share the same building structure with just different syntax. This means with a slight change to syntax you can get the same thing in Dashimo or PSWriteHTML directly.
$DomainControllers = Get-WinADDomainControllers -TestAvailability New-HTML -UseCssLinks -UseJavaScriptLinks { New-HTMLTable -DataTable $DomainControllers { New-HTMLTableHeader -Names 'IPV4Address', 'IPV6Address' -Title 'Ip Addresses' -Alignment center -Color White -BackGroundColor Gray New-HTMLTableHeader -Names 'SchemaMaster', 'PDCEmulator', 'RIDMaster', 'DomainNamingMasterMaster', 'InfrastructureMaster' -Title 'Roles' -Alignment center -Color White -BackGroundColor Gray New-HTMLTableHeader -Names 'LdapPort', 'SslPort' -Title 'Ports' -Alignment center -Color White -BackGroundColor Gray New-HTMLTableCondition -ComparisonType 'string' -Name 'SchemaMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline New-HTMLTableCondition -ComparisonType 'string' -Name 'PDCEmulator' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline New-HTMLTableCondition -ComparisonType 'string' -Name 'RIDMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline New-HTMLTableCondition -ComparisonType 'string' -Name 'IsReadOnly' -Operator eq -Value 'True' -BackgroundColor Yellow -Color Black -Inline New-HTMLTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline New-HTMLTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator ne -Value 'True' -BackgroundColor Red -Color White -Inline } -HideFooter } -FilePath $PSScriptRoot\TestMyHTML.html -ShowHTML -TitleText 'My test'
Similar code gives you somewhat similar results. Of course, the difference is that by default, PSWriteHTML generates HTML with JavaScript enabled. This will work fine in Chrome, New Edge or Firefox, giving you slightly more features that I've already covered in earlier blogs such as export to Excel, PDF, or advanced sorting. There's also somewhat different visual output since it's styled with original DataTables.net colors. New-HTMLTable contains additional switch Simplify. The purpose of this switch is that all that JavaScript functionality is disabled and you get what you would usually get when using Emailimo.
$DomainControllers = Get-WinADDomainControllers -TestAvailability New-HTML -UseCssLinks -UseJavaScriptLinks { New-HTMLTable -DataTable $DomainControllers { New-HTMLTableHeader -Names 'IPV4Address', 'IPV6Address' -Title 'Ip Addresses' -Alignment center -Color White -BackGroundColor Gray New-HTMLTableHeader -Names 'SchemaMaster', 'PDCEmulator', 'RIDMaster', 'DomainNamingMasterMaster', 'InfrastructureMaster' -Title 'Roles' -Alignment center -Color White -BackGroundColor Gray New-HTMLTableHeader -Names 'LdapPort', 'SslPort' -Title 'Ports' -Alignment center -Color White -BackGroundColor Gray New-HTMLTableCondition -ComparisonType 'string' -Name 'SchemaMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline New-HTMLTableCondition -ComparisonType 'string' -Name 'PDCEmulator' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline New-HTMLTableCondition -ComparisonType 'string' -Name 'RIDMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline New-HTMLTableCondition -ComparisonType 'string' -Name 'IsReadOnly' -Operator eq -Value 'True' -BackgroundColor Yellow -Color Black -Inline New-HTMLTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline New-HTMLTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator ne -Value 'True' -BackgroundColor Red -Color White -Inline } -HideFooter -Simplify } -FilePath $PSScriptRoot\TestMyHTML.html -ShowHTML -TitleText 'My test'
This Simplify functionality may seem like a useless gimmick, but it isn't if you take into consideration the next feature I wanted to show you.
New-HTMLTableCondition is excellent functionality which allows you to quickly and easily style your content based on conditions without doing any work by yourself. This functionality is based on New-HTMLTableContent function, which I want to introduce in this part of the blog. This function is a bit like New-HTMLTableHeader. It allows you to style or merge the content of a table. Let's take a look at an example below.
Import-Module .\PSWriteHTML.psd1 -Force $Properties = @( 'Name' 'Id' 'PriorityClass' 'FileVersion' 'HandleCount' 'WorkingSet' 'PagedMemorySize' 'PrivateMemorySize' 'VirtualMemorySize' 'TotalProcessorTime' ) $ProcessesAll = Get-Process | Select-Object -First 10 $Processes = $ProcessesAll | Select-Object -First 10 -Property $Properties New-HTML -TitleText 'Title' -UseCssLinks:$true -UseJavaScriptLinks:$true -FilePath $PSScriptRoot\Example24_01.html -ShowHTML { New-HTMLContent -HeaderText '0 section' { New-HTMLPanel { New-HTMLTable -DataTable $Processes -HideFooter -ScrollCollapse { New-HTMLTableContent -Names 'Name' -RowIndex 5 -BackGroundColor Blue New-HTMLTableContent -Names 'Name' -RowIndex 1 -BackGroundColor Yellow New-HTMLTableContent -Names 'HandleCount' -RowIndex 1 -BackGroundColor Yellow New-HTMLTableContent -ColumnIndex 7 -RowIndex 4 -BackGroundColor Gold -Color Blue -FontStyle italic } } } }
As you can see this time I'm operating on Column Names and Row Index. You can also operate on Column Index instead of Column Name if it's something you prefer. And then you can simply tell it to apply any styling you want. From colors, to making it bold or italic. Up to you.
Nice right? But that's not all. You can also use it to replace text within table.
Import-Module .\PSWriteHTML.psd1 -Force $Properties = @( 'Name' 'Id' 'PriorityClass' 'FileVersion' 'HandleCount' 'WorkingSet' 'PagedMemorySize' 'PrivateMemorySize' 'VirtualMemorySize' 'TotalProcessorTime' ) $ProcessesAll = Get-Process | Select-Object -First 10 $Processes = $ProcessesAll | Select-Object -First 10 -Property $Properties New-HTML -TitleText 'Title' -UseCssLinks:$true -UseJavaScriptLinks:$true -FilePath $PSScriptRoot\Example24_01.html -ShowHTML { New-HTMLContent -HeaderText '0 section' { New-HTMLPanel { New-HTMLTable -DataTable $Processes -HideFooter -ScrollCollapse { New-HTMLTableContent -Names 'Name' -RowIndex 5 -BackGroundColor Blue New-HTMLTableContent -Names 'Name' -RowIndex 1 -BackGroundColor Yellow New-HTMLTableContent -Names 'HandleCount' -RowIndex 1 -BackGroundColor Yellow New-HTMLTableContent -ColumnIndex 7 -RowIndex 4 -BackGroundColor Gold -Color Blue -FontStyle italic -Text "I HAVE REPLACED THIS" -FontSize 15 } } } }
That's done by using Text parameter. Easy right? Useful? Well, it has it's use cases, which I will show you in a second. Take a look at example below where I'm replacing Text that spans across 2 rows and 3 columns.
Cool right? Of course, in case of this example, it's useless, but you can use this functionality in Emails or Reports as you want.
Import-Module .\PSWriteHTML.psd1 -Force $Properties = @( 'Name' 'Id' 'PriorityClass' 'FileVersion' 'HandleCount' 'WorkingSet' 'PagedMemorySize' 'PrivateMemorySize' 'VirtualMemorySize' 'TotalProcessorTime' ) $ProcessesAll = Get-Process | Select-Object -First 10 $Processes = $ProcessesAll | Select-Object -First 10 -Property $Properties New-HTML -TitleText 'Title' -UseCssLinks:$true -UseJavaScriptLinks:$true -FilePath $PSScriptRoot\Example24_01.html -ShowHTML { New-HTMLContent -HeaderText '0 section' { New-HTMLPanel { New-HTMLTable -DataTable $Processes -HideFooter -ScrollCollapse { New-HTMLTableContent -Names 'Name' -RowIndex 5 -BackGroundColor Blue New-HTMLTableContent -Names 'Name' -RowIndex 1 -BackGroundColor Yellow New-HTMLTableContent -Names 'HandleCount' -RowIndex 1 -BackGroundColor Yellow New-HTMLTableContent -ColumnIndex 7,8,9 -RowIndex 4,5 -BackGroundColor Gold -Color Blue -FontStyle italic -Text "I HAVE REPLACED THIS" -FontSize 15 -Alignment center } -Simplify } } }
It's important to notice two things. Replacement line shows you how to do merging and styling, but also how I've used Simplify parameter for New-HTMLTable. It's not there by accident. When you enable merging within a table JavaScript functionality related to sorting, filtering, resizing is broken because it would need to consider that some columns/rows are now joined together which is not something that can easily be fixed. Until DataTables.net adds this functionality in their code, you need to use Simplify switch if you want to utilize merging functionality within table content. Text replacement, styling all that works without that switch. Just joining is affected. Of course, this functionality is identical in Emailimo or Dashimo.
You can use EmailTableContent or TableContent to apply to style in Dashimo or Emailimo. In emailimo, it even makes more sense to merge cells.
Import-Module Emailimo -Force $DomainControllers = Get-WinADDomainControllers -TestAvailability $Data = @( [PSCustomObject] @{ Name = 'Test 1'; 'OtherData' = 'Special Values'; 'Value' = 57 } [PSCustomObject] @{ Name = 'Test 1'; 'OtherData' = 'Special Values'; 'Value' = 105 } [PSCustomObject] @{ Name = 'Test 1'; 'OtherData' = 'Special Values'; 'Value' = 20 } [PSCustomObject] @{ Name = 'Test 1'; 'OtherData' = 'Special Values'; 'Value' = 11 + 20 + 105 + 57 } ) Email { EmailHeader { EmailFrom -Address '[email protected]' EmailTo -Addresses "[email protected]" EmailServer -Server 'smtp.office365' -UserName 'rpassword' -Password 'C:\Support\Important\Password-Evotec-Reminder.txt' -PasswordAsSecure -PasswordFromFile EmailOptions -Priority High -DeliveryNotifications Never EmailSubject -Subject 'This is a test email' } EmailBody -FontFamily 'Calibri' -Size 15 { EmailText -Text "Hello ", $UserNotify, "," -Color None, Blue, None -Verbose -LineBreak EmailText -Text "Domain Controllers ", 'Status' -Color Blue, None EmailTable -DataTable $DomainControllers { EmailTableHeader -Names 'IPV4Address', 'IPV6Address' -Title 'Ip Addresses' -Alignment center -Color White -BackGroundColor Gray EmailTableHeader -Names 'SchemaMaster', 'PDCEmulator', 'RIDMaster', 'DomainNamingMasterMaster', 'InfrastructureMaster' -Title 'Roles' -Alignment center -Color White -BackGroundColor Gray EmailTableHeader -Names 'LdapPort', 'SslPort' -Title 'Ports' -Alignment center -Color White -BackGroundColor Gray EmailTableCondition -ComparisonType 'string' -Name 'SchemaMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'PDCEmulator' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'RIDMaster' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'IsReadOnly' -Operator eq -Value 'True' -BackgroundColor Yellow -Color Black -Inline EmailTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline EmailTableCondition -ComparisonType 'string' -Name 'Pingable' -Operator ne -Value 'True' -BackgroundColor Red -Color White -Inline } -HideFooter EmailText -LineBreak EmailTable -DataTable $Data { EmailTableContent -RowIndex 4 -ColumnIndex 1, 2 -Text 'Summary' -Color Black -BackGroundColor Gray } -HideFooter EmailText -LineBreak EmailTextBox { 'Kind regards,' 'Evotec IT' } } } -AttachSelf
Since JavaScript in Email is disabled merging doesn't require you to define Simplify parameter. However, keep in mind that when you use AttachSelf switch (as mentioned in my other blog) which attaches Email Content as an attachment – this attachment is JS Enabled. That means when you open up it in Chrome, and you've used merging without Simplify parameter for your tables, it will be broken JS just like I've described above. For small tables, you may not notice a problem, but for larger ones, the functionality you expected will not be there. So just a fair warning. Otherwise, be my guest and use this as required.
You can, of course, mix and match all the options together. Having New-HTMLTableHeader, New-HTMLTableCondition, and New-HTMLTableContent all applying their styles and changes as you need to. While the tables below don't look pretty, I just wanted to show you that it's not a problem to have a lot of additional formatting. And since we don't have a massive amount of data to process on my computer, it's still around 2 seconds.
Import-Module PSWriteHTML -Force $Properties = @( 'Name' 'Id' 'PriorityClass' 'FileVersion' 'HandleCount' 'WorkingSet' 'PagedMemorySize' 'PrivateMemorySize' 'VirtualMemorySize' 'TotalProcessorTime' ) $ProcessesAll = Get-Process | Select-Object -First 10 $Processes = $ProcessesAll | Select-Object -First 10 -Property $Properties New-HTML -TitleText 'Title' -UseCssLinks:$true -UseJavaScriptLinks:$true -FilePath $PSScriptRoot\Example24.html -ShowHTML { New-HTMLContent -HeaderText 'Section 1' { New-HTMLContainer { New-HTMLPanel { New-HTMLTable -DataTable $Processes -HideFooter -ScrollCollapse { New-HTMLTableHeader -Names 'Name', 'ID' -Title 'Process Information' -Color Red -FontWeight lighter -Alignment left -BackGroundColor LightBlue New-HTMLTableHeader -Names 'PagedMemorySize', 'PrivateMemorySize', 'VirtualMemorySize' -Title 'Memory' -Color White -BackGroundColor Blue New-HTMLTableHeader -Names 'Name' -BackGroundColor Red -Color WhiteSmoke New-HTMLTableHeader -Names 'Id' -BackGroundColor Blue -Color White New-HTMLTableHeader -Names 'PriorityClass', 'FileVersion', 'HandleCount' -BackGroundColor Gold -Color White New-HTMLTableHeader -BackGroundColor Green -Color White -Title 'Full Title' New-HTMLTableCondition -Name 'HandleCount' -ComparisonType number -Operator gt -Value 500 -BackgroundColor Gray -Color White New-HTMLTableCondition -Name 'FileVersion' -ComparisonType string -Operator eq -Value '1.36.1' -BackgroundColor Gold -Color White -FontWeight bold -Alignment center -TextDecoration line-through New-HTMLTableContent -Names 'Name' -RowIndex 5 -BackGroundColor Blue New-HTMLTableContent -Names 'Name' -RowIndex 1 -BackGroundColor Yellow New-HTMLTableContent -Names 'HandleCount' -RowIndex 1 -BackGroundColor Yellow } } New-HTMLPanel { New-HTMLTable -DataTable $Processes -HideFooter -Simplify { New-HTMLTableContent -ColumnIndex 2, 3, 4, 5 -RowIndex 7, 6 -Text 'Replace 4 columns, over 2 rows' -Alignment center -Color Green -FontWeight bold -BackGroundColor Yellow } } } } New-HTMLContent -HeaderText 'Section 2' { New-HTMLPanel { New-HTMLTable -DataTable $Processes -HideFooter -ScrollCollapse { New-HTMLTableHeader -Names 'Name', 'ID' -Title 'Process Information' -Color Red -FontWeight lighter -Alignment left -BackGroundColor LightBlue New-HTMLTableHeader -Names 'PagedMemorySize', 'PrivateMemorySize', 'VirtualMemorySize' -Title 'Memory' -Color White -BackGroundColor Blue New-HTMLTableHeader -Names 'Name' -BackGroundColor Red -Color WhiteSmoke New-HTMLTableHeader -Names 'Id' -BackGroundColor Blue -Color White New-HTMLTableHeader -Names 'PriorityClass', 'FileVersion', 'HandleCount' -BackGroundColor Gold -Color White New-HTMLTableHeader -BackGroundColor Green -Color White -Title 'Full Title' New-HTMLTableCondition -Name 'HandleCount' -ComparisonType number -Operator gt -Value 500 -BackgroundColor Gray -Color White New-HTMLTableCondition -Name 'FileVersion' -ComparisonType string -Operator eq -Value '1.36.1' -BackgroundColor Gold -Color White -FontWeight bold -Alignment center -TextDecoration line-through New-HTMLTableContent -RowIndex 5, 3 -BackGroundColor Blue -Color White New-HTMLTableContent -ColumnName 'Name', 'PagedMemorySize' -BackGroundColor Gold -Color Green New-HTMLTableContent -ColumnIndex 5, 2 -BackGroundColor Green -Color White New-HTMLTableContent -ColumnIndex 2 -RowIndex 2 -Text 'Test' -Alignment center -Color Green -FontWeight bold -BackGroundColor Yellow New-HTMLTableContent -ColumnIndex 2 -RowIndex 5 -Text 'Test 1' -Alignment center -Color Green -FontWeight bold -BackGroundColor Yellow New-HTMLTableContent -ColumnIndex 2, 3, 4, 5 -RowIndex 7, 6 -Text 'Replace 4 columns' -Alignment center -Color Green -FontWeight bold -BackGroundColor Yellow New-HTMLTableContent -Names 'Name' -RowIndex 5 -BackGroundColor Blue New-HTMLTableContent -Names 'Name' -RowIndex 1 -BackGroundColor Yellow New-HTMLTableContent -Names 'HandleCount' -RowIndex 1 -BackGroundColor Yellow -FontStyle italic New-HTMLTableContent -Names 'HandleCount' -RowIndex 2 -BackGroundColor Blue -Color White -FontStyle italic New-HTMLTableContent -ColumnIndex 8 -RowIndex 2 -BackGroundColor Blue -Color White -FontStyle italic New-HTMLTableContent -ColumnIndex 3 -RowIndex 6 -BackGroundColor Blue -Color White -FontStyle italic New-HTMLTableContent -ColumnIndex 1 -RowIndex 4 -BackGroundColor Blue -Color White -FontStyle italic New-HTMLTableCondition -Name 'FileVersion' -ComparisonType string -Operator eq -Value '1.36.1' -BackgroundColor Gold -Color White -FontWeight bold -Alignment center -TextDecoration line-through -Inline New-HTMLTableCondition -Name 'FileVersion' -ComparisonType string -Operator eq -Value '10.1905.30.0' -BackgroundColor Green -Color White -FontWeight bold -Alignment center -TextDecoration line-through -Inline -Row New-HTMLTableCondition -Name 'FileVersion' -ComparisonType string -Operator eq -Value '1.36.1' -BackgroundColor Red -Color White -FontWeight bold -Inline -row New-HTMLTableCondition -Name 'Name' -Operator ne -Value 'AppVShNotify' -BackgroundColor Red -Color White -Inline -row New-HTMLTableCondition -ComparisonType 'number' -Name 'Id' -Operator gt -Value 15000 -BackgroundColor Green -Color White -Inline #-row New-HTMLTableCondition -ComparisonType 'number' -Name 'Id' -Operator lt -Value 15000 -BackgroundColor Red -Color White -Inline #-row New-HTMLTableCondition -ComparisonType string -Name 'Name' -Operator eq -Value 'Code' -BackgroundColor blue -Color White -Inline -row } } } }
As I've explained before, when you use Inline switch, it will impact generation time. For smaller tables, it shouldn't be noticeable, for larger ones it may add a few seconds here and there depending on what you will be doing if you decide to apply new formatting for every single row it has to go and fix style per each Table Cell. You can see for yourself how the last table looks if you apply a lot of formatting to it.
<table id="DT-ddCbWNYX" class="display compact"> <thead> <tr> <th style="color:#ffffff;background-color:#008000" colspan="10">Full Title</th> </tr> <tr> <th style="color:#ff0000;background-color:#add8e6;text-align:left;font-weight:lighter" colspan="2">Process Information</th> <th style="color:#ffffff;background-color:#ffd700" rowspan="2">PriorityClass </th> <th style="color:#ffffff;background-color:#ffd700" rowspan="2">FileVersion</th> <th style="color:#ffffff;background-color:#ffd700" rowspan="2">HandleCount</th> <th rowspan="2">WorkingSet</th> <th style="color:#ffffff;background-color:#0000ff" colspan="3">Memory</th> <th rowspan="2">TotalProcessorTime</th> </tr> <tr> <th style="color:#f5f5f5;background-color:#ff0000">Name</th> <th style="color:#ffffff;background-color:#0000ff">Id</th> <th>PagedMemorySize</th> <th>PrivateMemorySize</th> <th>VirtualMemorySize</th> </tr> </thead> <tbody> <tr> <td style="color:#ffffff;background-color:#ff0000">1Password</td> <td style="color:#ffffff;background-color:#008000">17180</td> <td style="color:#ffffff;background-color:#ff0000">Normal</td> <td style="color:#ffffff;background-color:#ff0000">7.3.684</td> <td style="color:#ffffff;background-color:#ff0000">762</td> <td style="color:#ffffff;background-color:#ff0000">11976704</td> <td style="color:#ffffff;background-color:#ff0000">44621824</td> <td style="color:#ffffff;background-color:#ff0000">44621824</td> <td style="color:#ffffff;background-color:#ff0000">296759296</td> <td style="color:#ffffff;background-color:#ff0000">00:00:01.5000000</td> </tr> <tr> <td style="color:#ffffff;background-color:#ff0000">aesm_service</td> <td style="color:#ffffff;background-color:#008000">25340</td> <td style="color:#ffffff;background-color:#ff0000"></td> <td style="color:#ffffff;background-color:#ff0000"></td> <td style="color:#ffffff;background-color:#ff0000">189</td> <td style="color:#ffffff;background-color:#ff0000">3497984</td> <td style="color:#ffffff;background-color:#ff0000">2482176</td> <td style="color:#ffffff;background-color:#ff0000">2482176</td> <td style="color:#ffffff;background-color:#ff0000">94785536</td> <td style="color:#ffffff;background-color:#ff0000"></td> </tr> <tr> <td style="color:#ffffff;background-color:#ff0000">ApplicationFrameHost</td> <td style="color:#ffffff;background-color:#ff0000">2020</td> <td style="color:#ffffff;background-color:#ff0000">Normal</td> <td style="color:#ffffff;background-color:#ff0000">10.0.18362.1 (WinBuild.160101.0800)</td> <td style="color:#ffffff;background-color:#ff0000">488</td> <td style="color:#ffffff;background-color:#ff0000">9154560</td> <td style="color:#ffffff;background-color:#ff0000">23728128</td> <td style="color:#ffffff;background-color:#ff0000">23728128</td> <td style="color:#ffffff;background-color:#ff0000">271568896</td> <td style="color:#ffffff;background-color:#ff0000">00:00:00.5937500</td> </tr> <tr> <td style="color:#ffffff;background-color:#ff0000">audiodg</td> <td style="color:#ffffff;background-color:#008000">30916</td> <td style="color:#ffffff;background-color:#ff0000"></td> <td style="color:#ffffff;background-color:#ff0000"></td> <td style="color:#ffffff;background-color:#ff0000">177</td> <td style="color:#ffffff;background-color:#ff0000">12255232</td> <td style="color:#ffffff;background-color:#ff0000">6754304</td> <td style="color:#ffffff;background-color:#ff0000">6754304</td> <td style="color:#ffffff;background-color:#ff0000">86020096</td> <td style="color:#ffffff;background-color:#ff0000">00:00:00.7968750</td> </tr> <tr> <td style="color:#ffffff;background-color:#008000;text-align:center;text-decoration:line-through;font-weight:bold"> Calculator</td> <td style="color:#ffffff;background-color:#008000;text-align:center;text-decoration:line-through;font-weight:bold"> 2748</td> <td style="color:#ffffff;background-color:#008000;text-align:center;text-decoration:line-through;font-weight:bold"> Normal</td> <td style="color:#ffffff;background-color:#008000;text-align:center;text-decoration:line-through;font-weight:bold"> 10.1905.30.0</td> <td style="color:#ffffff;background-color:#008000;text-align:center;text-decoration:line-through;font-weight:bold"> 575</td> <td style="color:#ffffff;background-color:#008000;text-align:center;text-decoration:line-through;font-weight:bold"> 548864</td> <td style="color:#ffffff;background-color:#008000;text-align:center;text-decoration:line-through;font-weight:bold"> 36745216</td> <td style="color:#ffffff;background-color:#008000;text-align:center;text-decoration:line-through;font-weight:bold"> 36745216</td> <td style="color:#ffffff;background-color:#008000;text-align:center;text-decoration:line-through;font-weight:bold"> 548401152</td> <td style="color:#ffffff;background-color:#008000;text-align:center;text-decoration:line-through;font-weight:bold"> 00:00:00.2343750</td> </tr> <tr> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">Code</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">6644</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">Normal</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">1.36.1</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">236</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">8126464</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">28364800 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">28364800 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">413822976 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold"> 00:00:00.1718750</td> </tr> <tr> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">Code</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">9332</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">Normal</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">1.36.1</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">1257</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">70463488 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">63787008 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">63787008 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">684625920 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold"> 00:19:37.2656250</td> </tr> <tr> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">Code</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">11648</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">Normal</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">1.36.1</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">389</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">17711104 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">186875904 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">186875904 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">1712168960 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold"> 00:00:05.0781250</td> </tr> <tr> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">Code</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">13468</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">Normal</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">1.36.1</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">399</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">18501632 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">248168448 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">248168448 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">1771180032 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold"> 00:01:04.2968750</td> </tr> <tr> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">Code</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">15888</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">Normal</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">1.36.1</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">407</td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">329633792 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">430379008 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">430379008 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold">1986240512 </td> <td style="color:#ffffff;background-color:#ff0000;font-weight:bold"> 00:02:21.5625000</td> </tr> </tbody> </table>
While working on the new version I've updated how I create „Toast” messages, I've not written about this feature anywhere, but it was used as part of Statusimo module. I've rewritten it and now added the ability to use any of 1000+ icons, and choose colors for the left bar, right bar, header text, text giving you a personal touch. Not sure how useful it will be in your scenarios, but it's there if you need it.
The code is straightforward. If you know how to use Panels/Sections and Tables put a toast inside one of those and you should be fine.
$Processes = Get-Process | Select-Object -First 20 New-HTML -TitleText 'Title' -UseCssLinks -UseJavaScriptLinks -FilePath $PSScriptRoot\Example25.html { New-HTMLContent -Invisible { New-HTMLPanel -Invisible { New-HTMLToast -TextHeader 'Maintenance' -Text "We've planned maintenance on 24th of January 2020. It will last 30 hours." -IconRegular address-card } New-HTMLPanel -Invisible { New-HTMLToast -TextHeader 'Maintenance' -Text "We've planned maintenance on 24th of January 2020. It will last 30 hours." -IconColor DarkGrey -BarColorLeft ForestGreen -TextColor Gainsboro -IconBrands 500px } } New-HTMLContent -Invisible { New-HTMLTable -DataTable $Processes -HideFooter } New-HTMLPanel -Invisible { New-HTMLToast -TextHeader 'Maintenance' -Text "We've planned maintenance on 24th of January 2020. It will last 30 hours." -IconColor AliceBlue -BarColorLeft Grey -TextHeaderColor Gold -IconRegular eye New-HTMLToast -TextHeader 'Maintenance' -Text "We've planned maintenance on 24th of January 2020. It will last 30 hours." -IconBrands app-store New-HTMLToast -TextHeader 'Maintenance' -Text "We've planned maintenance on 24th of January 2020. It will last 30 hours." -IconRegular surprise New-HTMLToast -TextHeader 'Maintenance' -Text "We've planned maintenance on 24th of January 2020. It will last 30 hours." -IconSolid bell New-HTMLToast -TextHeader 'Maintenance' -Text "We've planned maintenance on 24th of January 2020. It will last 30 hours." -IconBrands cloudsmith New-HTMLToast -TextHeader 'Maintenance' -Text "We've planned maintenance on 24th of January 2020. It will last 30 hours." -IconBrands accessible-icon New-HTMLToast -TextHeader 'Maintenance' -Text "We've planned maintenance on 24th of January 2020. It will last 30 hours." -IconBrands accusoft -BarColorRight DarkTurquoise } } -ShowHTML
Finally, I wanted also to let you know about two other features PSWriteHTML is capable of and something that Statusimo is currently using. Those are Status Messages and Timeline. You can see the StatusItem buttons under the Current Status section and Timeline is visible under Past Incidents section. Their current state is „it works,” but I don't like how it's built and how it looks so you may expect those features to be changed/expanded.
Code below is all it takes to create Status Page as above. Feel free to create your own Status Pages or use Statusimo the way it was written.
New-HTML -TitleText 'Services Status' -UseCssLinks -UseJavaScriptLinks { New-HTMLSection -Invisible { New-HTMLContainer -Width '900px' -Margin 'auto' { New-HTMLPanel -Invisible { New-HTMLToast -TextHeader 'Information' -Text 'Everything is running smoothly!' -BarColorLeft Blue -IconSolid info-circle -IconColor Blue } New-HTMLHeading -Heading h1 -HeadingText 'Current Status' New-HTMLPanel -Invisible { New-HTMLStatus { New-HTMLStatusItem -ServiceName 'Active Directory' -ServiceStatus 'Operational' -Icon Good -Percentage '100%' New-HTMLStatusItem -ServiceName 'Github' -ServiceStatus 'Operational' -Icon Good -Percentage '100%' New-HTMLStatusItem -ServiceName 'Hyper-V' -ServiceStatus 'Non-functional' -Icon Dead -Percentage '0%' New-HTMLStatusItem -ServiceName 'Active Directory' -ServiceStatus 'Operational' -Icon Good -Percentage '100%' New-HTMLStatusItem -ServiceName 'Azure' -ServiceStatus 'Working on it' -Icon Bad -Percentage '70%' New-HTMLStatusItem -ServiceName 'Twitter' -ServiceStatus 'Who knows?!' -Icon Bad -Percentage '30%' } } New-HTMLHeading -Heading h1 -HeadingText 'Scheduled Maintenance' New-HTMLPanel -Invisible { New-HTMLToast -TextHeader 'Maintenance' -Text "We've planned maintenance on 24th of January 2020. It will last 30 hours." -BarColorLeft OrangeRed -IconSolid info-circle -IconColor OrangeRed } New-HTMLHeading -Heading h1 -HeadingText 'Incidents per day' New-HTMLPanel -Invisible { $Data = foreach ($Element in 30..0) { Get-Random -Minimum 0 -Maximum 5 } $DataName = "Incidents" $DataCategories = foreach ($Element in 30..0) { (Get-Date).AddDays(-$Element).ToShortDateString() } New-HTMLChart -Title 'Incidents per day' -TitleAlignment left { New-ChartCategory -Name $DataCategories New-ChartLine -Name $DataName -Value $Data } } New-HTMLHeading -Heading h1 -HeadingText 'Past Incidents' New-HTMLPanel -Invisible { New-HTMLTimeline { New-HTMLTimelineItem -HeadingText 'Azure AD is down' -Text "We have huge problems" -Date (Get-Date).AddDays(-1) New-HTMLTimelineItem -HeadingText 'Azure AD ...' -Text "We have huge problems" -Date (Get-Date).AddDays(-2) New-HTMLTimelineItem -HeadingText 'AD just died' -Text "We have huge problems" -Date (Get-Date).AddDays(-3) New-HTMLTimelineItem -HeadingText 'Twitter' -Text "We're very soorrry! It just won't work!" -Date (Get-Date).AddDays(-4) New-HTMLTimelineItem -HeadingText 'Twitter' -Text "We're very soorrry! It just won't work!" -Date (Get-Date).AddDays(-4) New-HTMLTimelineItem -HeadingText 'Twitter' -Text "We're very soorrry! It just won't work!" -Date (Get-Date).AddDays(-4) New-HTMLTimelineItem -HeadingText 'Twitter' -Text "We're very soorrry! It just won't work!" -Date (Get-Date).AddDays(-5) New-HTMLTimelineItem -HeadingText 'Twitter' -Text "We're very soorrry! It just won't work!" -Date (Get-Date).AddDays(-6) New-HTMLTimelineItem -HeadingText 'Twitter' -Text "We're very soorrry! It just won't work!" -Date (Get-Date).AddDays(-7) } } } } } -FilePath $PSScriptRoot\StatusPage04.html -ShowHTML
For easy use and installation, all modules are available from PowerShellGallery. Installing it is as easy as it gets. Keep in mind that when you install Emailimo, you get PSWriteHTML installed by default, so you don't have to install it separately. Same goes for Dashimo or Statusimo.
Install-Module PSWriteHTML -AllowClobber -Force Install-Module Dashimo -AllowClobber -Force Install-Module Statusimo -AllowClobber -Force Install-Module Emailimo -AllowClobber -Force
Code as always is stored on GitHub and is free to use and take.