PowerShell

Formatting and minifying resources (HTML, CSS, JavaScript) with PowerShell

When you work with HTML, CSS, and JavaScript, you often meet three versions on how those are stored in files – minified, formatted, somewhere in the middle (usually a total mess). I have all three versions in my PSWriteHTML module. Some are minified 3rd party resources, some are generated by my PowerShell commands (and are a total mess when it comes to formatting), and finally, some are formatted resources by using built-in VSCode features. In whatever form they are, they generally have no impact on how browsers display them. Browsers will read them in any kind and not care for how they look.

Formatting and Optimizations

While formatting doesn't matter for browsers, it's much easier to debug, change HTML, CSS or JavaScript if it's properly formatted code. Trying to understand what happens in Minified JS script like the one below is far from optimal.

(function(){function main(){var tabButtons = [].slice.call(document.querySelectorAll("ul.tab-nav li a.buttonTab"));tabButtons.map(function(button){button.addEventListener("click",function(){document .querySelector("li a.active.buttonTab") .classList.remove("active");button.classList.add("active");document .querySelector(".tab-pane.active") .classList.remove("active");document .querySelector(button.getAttribute("href")) .classList.add("active")})})}if(document.readyState!== "loading"){main()}else{document.addEventListener("DOMContentLoaded",main)}})();

What if this could be formatted as code below?

(function() {
    function main() {
        var tabButtons = [].slice.call(document.querySelectorAll("ul.tab-nav li a.buttonTab"));
        tabButtons.map(function(button) {
            button.addEventListener("click", function() {
                document.querySelector("li a.active.buttonTab").classList.remove("active");
                button.classList.add("active");
                document.querySelector(".tab-pane.active").classList.remove("active");
                document.querySelector(button.getAttribute("href")).classList.add("active")
            })
        })
    }
    if (document.readyState !== "loading") {
        main()
    } else {
        document.addEventListener("DOMContentLoaded", main)
    }
})();

Much more beautiful, more comfortable to read and you can do some changes to it. Same goes for HTML. Often my PSWriteHTML module generates something like this:

<html><!-- HEADER --><head><meta charset="utf-8"/><meta content="width=device-width, initial-scale=1" name="viewport"/><meta name="author"/><meta content="2019-08-09 11:26:37" name="revised"/><title>My title</title><!-- CSS Default fonts START --><link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet"/><!-- CSS Default fonts END --><!-- CSS Default fonts icons START --><link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet"/><!-- CSS Default fonts icons END --><div class="flexElement overflowHidden"><table id="DT-hZRTQIVT" class="display compact"><thead><tr><th>Name</th><th class="none">Id</th><th class="none">HandleCount</th><th>WorkingSet</th></tr></thead><tbody><tr><td>1Password</td><td>22268</td><td>1007</td><td>87146496</td></tr><tr><td>aesm_service</td><td>25340</td><td>189</td><td>3948544</td></tr></tbody></table></div></div></div></div></body><!-- END BODY --><!-- FOOTER --><footer></footer><!-- END FOOTER --></html>

Which is hard to understand and modify. Let's check the formatted version:

<html>
        <!-- HEADER -->
        <head>
                <meta charset="utf-8">
                <meta content="width=device-width, initial-scale=1" name="viewport">
                <meta name="author">
                <meta content="2019-08-09 11:26:37" name="revised">
                <title>My title</title>
                <!-- CSS Default fonts START -->
                <link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet">
                <!-- CSS Default fonts END -->
                <!-- CSS Default fonts icons START -->
                <link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet">
                <!-- CSS Default fonts icons END -->
        </head>
        <body>
                <div class="flexElement overflowHidden">
                        <table id="DT-hZRTQIVT" class="display compact">
                                <thead>
                                        <tr>
                                                <th>Name</th>
                                                <th class="none">Id</th>
                                                <th class="none">HandleCount</th>
                                                <th>WorkingSet</th>
                                        </tr>
                                </thead>
                                <tbody>
                                        <tr>
                                                <td>1Password</td>
                                                <td>22268</td>
                                                <td>1007</td>
                                                <td>87146496</td>
                                        </tr>
                                        <tr>
                                                <td>aesm_service</td>
                                                <td>25340</td>
                                                <td>189</td>
                                                <td>3948544</td>
                                        </tr>
                                </tbody>
                        </table>
                </div>
                <footer></footer>
                <!-- END FOOTER -->
        </body>
        <!-- END BODY -->
        <!-- FOOTER -->
</html>

Of course, you can do that formatting with VSCode, or you can use some online converter to do it for you, but I thought how hard it would be to do it in PowerShell?

How to Optimize (Minify) or Format HTML, CSS and JavaScript with PowerShell

Turns out with some help of NET libraries you can Format HTML, JavaScript, and CSS. You can also reverse that process and enable minification of those resources. Say hello to PSParseHTML. This little PowerShell module can do two things (for now).

  • Format HTML, JavaScript and CSS
  • Optimize (Minify) HTML, JavaScript and CSS

It can do so thru six different functions

  • Optimize-CSS
  • Optimize-HTML
  • Optimize-JavaScript
  • Format-CSS
  • Format-HTML
  • Format-JavaScript
They expect input as a string or from a file. They output to string or to file. I'm going to focus only on the input and output to string, but the idea is generally the same.
Formatting HTML with PowerShell

Let's see how this works when trying to format (prettify) HTML

$HTMLContent = '<html><!-- HEADER --><head><meta charset="utf-8"/><meta content="width=device-width, initial-scale=1" name="viewport"/><meta name="author"/><meta content="2019-08-09 11:26:37" name="revised"/><title>My title</title><!-- CSS Default fonts START --><link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet"/><!-- CSS Default fonts END --><!-- CSS Default fonts icons START --><link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet"/><!-- CSS Default fonts icons END --><div class="flexElement overflowHidden"><table id="DT-hZRTQIVT" class="display compact"><thead><tr><th>Name</th><th class="none">Id</th><th class="none">HandleCount</th><th>WorkingSet</th></tr></thead><tbody><tr><td>1Password</td><td>22268</td><td>1007</td><td>87146496</td></tr><tr><td>aesm_service</td><td>25340</td><td>189</td><td>3948544</td></tr></tbody></table></div></div></div></div></body><!-- END BODY --><!-- FOOTER --><footer></footer><!-- END FOOTER --></html>'

Format-HTML -Content $HTMLContent

Output

<html>
        <!-- HEADER -->
        <head>
                <meta charset="utf-8">
                <meta content="width=device-width, initial-scale=1" name="viewport">
                <meta name="author">
                <meta content="2019-08-09 11:26:37" name="revised">
                <title>My title</title>
                <!-- CSS Default fonts START -->
                <link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet">
                <!-- CSS Default fonts END -->
                <!-- CSS Default fonts icons START -->
                <link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet">
                <!-- CSS Default fonts icons END -->
        </head>
        <body>
                <div class="flexElement overflowHidden">
                        <table id="DT-hZRTQIVT" class="display compact">
                                <thead>
                                        <tr>
                                                <th>Name</th>
                                                <th class="none">Id</th>
                                                <th class="none">HandleCount</th>
                                                <th>WorkingSet</th>
                                        </tr>
                                </thead>
                                <tbody>
                                        <tr>
                                                <td>1Password</td>
                                                <td>22268</td>
                                                <td>1007</td>
                                                <td>87146496</td>
                                        </tr>
                                        <tr>
                                                <td>aesm_service</td>
                                                <td>25340</td>
                                                <td>189</td>
                                                <td>3948544</td>
                                        </tr>
                                </tbody>
                        </table>
                </div>
                <footer></footer>
                <!-- END FOOTER -->
        </body>
        <!-- END BODY -->
        <!-- FOOTER -->
</html>
Formatting CSS with PowerShell

Let's see how this works when trying to format (prettify) CSS

$CSS = '.tabsWrapper{text-align:center;margin:10px auto;font-family:"Roboto", sans-serif}.tabs{margin-top:10px;font-size:15px;padding:0;list-style:none;background:rgba(255, 255, 255, 1);box-shadow:0 5px 20px rgba(0, 0, 0, 0.1);border-radius:4px;position:relative}.tabs .round{border-radius:4px}.tabs a{text-decoration:none;color:rgba(119, 119, 119, 1);text-transform:uppercase;padding:10px 20px;display:inline-block;position:relative;z-index:1;transition-duration:0.6s}.tabs a.active{color:rgba(255, 255, 255, 1)}.tabs a i{margin-right:5px}.tabs .selector{display:none;height:100%;position:absolute;left:0;top:0;right:0;bottom:0;z-index:1;border-radius:4px}.tabs-content{display:none}.tabs-content.active{display:block}'

Format-CSS -Content $CSS

Output

.tabsWrapper { text-align: center; margin: 10px auto; font-family: "Roboto", sans-serif  }
.tabs { margin-top: 10px; font-size: 15px; padding: 0; list-style: none; background: rgba(255, 255, 255, 1); box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1); border-radius: 4px; position: relative }
.tabs .round { border-radius: 4px }
.tabs a { text-decoration: none; color: rgba(119, 119, 119, 1); text-transform: uppercase; padding: 10px 20px; display: inline-block; position: relative; z-index: 1; transition-duration: 0.6s }
.tabs a.active { color: rgba(255, 255, 255, 1) }
.tabs a i { margin-right: 5px }
.tabs .selector { display: none; height: 100%; position: absolute; left: 0; top: 0; right: 0; bottom: 0; z-index: 1; border-radius: 4px }
.tabs-content { display: none }
.tabs-content.active { display: block }
Formatting JavaScript with PowerShell

Let's see how this works when trying to format (prettify) JavaScript

$JSContent = '(function(){function main(){var tabButtons = [].slice.call(document.querySelectorAll("ul.tab-nav li a.buttonTab"));tabButtons.map(function(button){button.addEventListener("click",function(){document .querySelector("li a.active.buttonTab") .classList.remove("active");button.classList.add("active");document .querySelector(".tab-pane.active") .classList.remove("active");document .querySelector(button.getAttribute("href")) .classList.add("active")})})}if(document.readyState!== "loading"){main()}else{document.addEventListener("DOMContentLoaded",main)}})();'

Format-JavaScript -Content $JSContent

Output

(function() {
    function main() {
        var tabButtons = [].slice.call(document.querySelectorAll("ul.tab-nav li a.buttonTab"));
        tabButtons.map(function(button) {
            button.addEventListener("click", function() {
                document.querySelector("li a.active.buttonTab").classList.remove("active");
                button.classList.add("active");
                document.querySelector(".tab-pane.active").classList.remove("active");
                document.querySelector(button.getAttribute("href")).classList.add("active")
            })
        })
    }
    if (document.readyState !== "loading") {
        main()
    } else {
        document.addEventListener("DOMContentLoaded", main)
    }
})();
Optimizing (Minification) HTML with PowerShell

Let's see how this works when trying to minify HTML

$HTMLContentFormatted = @"
<html>
        <!-- HEADER -->
        <head>
                <meta charset="utf-8">
                <meta content="width=device-width, initial-scale=1" name="viewport">
                <meta name="author">
                <meta content="2019-08-09 11:26:37" name="revised">
                <title>My title</title>
                <!-- CSS Default fonts START -->
                <link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet">
                <!-- CSS Default fonts END -->
                <!-- CSS Default fonts icons START -->
                <link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet">
                <!-- CSS Default fonts icons END -->
        </head>
        <body>
                <div class="flexElement overflowHidden">
                        <table id="DT-hZRTQIVT" class="display compact">
                                <thead>
                                        <tr>
                                                <th>Name</th>
                                                <th class="none">Id</th>
                                                <th class="none">HandleCount</th>
                                                <th>WorkingSet</th>
                                        </tr>
                                </thead>
                                <tbody>
                                        <tr>
                                                <td>1Password</td>
                                                <td>22268</td>
                                                <td>1007</td>
                                                <td>87146496</td>
                                        </tr>
                                        <tr>
                                                <td>aesm_service</td>
                                                <td>25340</td>
                                                <td>189</td>
                                                <td>3948544</td>
                                        </tr>
                                </tbody>
                        </table>
                </div>
                <footer></footer>
                <!-- END FOOTER -->
        </body>
        <!-- END BODY -->
        <!-- FOOTER -->
</html>
"@

Optimize-HTML -Content $HTMLContentFormatted

Output

<html><head><meta charset=utf-8 /><meta content="width=device-width, initial-scale=1" name=viewport /><meta name=author /><meta content="2019-08-09 11:26:37" name=revised /><title>My title</title><link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type=text/css rel=stylesheet /><link href=https://use.fontawesome.com/releases/v5.7.2/css/all.css type=text/css rel=stylesheet /></head><body><div class="flexElement overflowHidden"><table id=DT-hZRTQIVT class="display compact"><thead><tr><th>Name</th><th class=none>Id</th><th 
class=none>HandleCount</th><th>WorkingSet</th></tr></thead><tbody><tr><td>1Password</td><td>22268</td><td>1007</td><td>87146496</td></tr><tr><td>aesm_service</td><td>25340</td><td>189</td><td>3948544</td></tr></tbody></table></div><footer></footer></body></html>

Good enough right?

Optimizing (Minification) JavaScript with PowerShell

Let's see how this works when trying to minify JavaScript (JS)

$JSCode = @"
function openTab(evt, tabName) {
    // Declare all variables
    var i, tabcontent, tablinks;

    // Get all elements with class="tabcontent" and hide them
    tabcontent = document.getElementsByClassName("insideTab");
    for (i = 0; i < tabcontent.length; i++) {
        tabcontent[i].style.display = "none";
    }

    // Get all elements with class="tablinks" and remove the class "active"
    tablinks = document.getElementsByClassName("tablinks");
    for (i = 0; i < tablinks.length; i++) {
        tablinks[i].className = tablinks[i].className.replace(" is-active", "");
    }

    // Show the current tab, and add an "active" class to the link that opened the tab
    try {
        document.getElementById(tabName).style.display = "block";
    } catch {
        // This error happens when someone forgot addng new TABS with proper names
        // Therefore style wouldn't exits on it.
    }
    evt.currentTarget.className += " active";
}
"@

Optimize-JavaScript -Content $JSCode

Output

function openTab(n,t){for(var r,u=document.getElementsByClassName("insideTab"),i=0;i<u.length;i++)u[i].style.display="none";for(r=document.getElementsByClassName("tablinks"),i=0;i<r.length;i++)r[i].className=r[i].className.replace(" is-active","");try{document.getElementById(t).style.display="block"}catch({}){.currentTarget.className+=" active"}}
Optimizing (Minification) CSS with PowerShell

Let's see how this works when trying to minify CSS

$CssCode = @"
.tabsWrapper {
    text-align: center;
    margin: 10px auto;
    font-family: 'Roboto', sans-serif ;
}

.tabs {
    margin-top: 10px;
    font-size: 15px;
    padding: 0px;
    list-style: none;
    background: #fff;
    box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.1);
    border-radius: 4px;
    position: relative;
}

.tabs .round {
    border-radius: 4px;
}

.tabs a {
    text-decoration: none;
    color: #777;
    text-transform: uppercase;
    padding: 10px 20px;
    display: inline-block;
    position: relative;
    z-index: 1;
    transition-duration: 0.6s;
}

.tabs a.active {
    color: #fff;
}

.tabs a i {
    margin-right: 5px;
}

.tabs .selector {
    display: none;
    height: 100%;
    position: absolute;
    left: 0px;
    top: 0px;
    right: 0px;
    bottom: 0px;
    z-index: 1;
    border-radius: 4px;
    background: ColorSelector;
}

.tabs-content {
    display: none;
}

.tabs-content.active {
    display: block;
}
"@

Optimize-CSS -Content $CssCode

Output

.tabsWrapper{text-align:center;margin:10px auto;font-family:"Roboto", sans-serif}.tabs{margin-top:10px;font-size:15px;padding:0;list-style:none;background:rgba(255, 255, 255, 1);box-shadow:0 5px 20px rgba(0, 0, 0, 0.1);border-radius:4px;position:relative}.tabs .round{border-radius:4px}.tabs a{text-decoration:none;color:rgba(119, 119, 119, 1);text-transform:uppercase;padding:10px 20px;display:inline-block;position:relative;z-index:1;transition-duration:0.6s}.tabs a.active{color:rgba(255, 255, 255, 1)}.tabs a i{margin-right:5px}.tabs .selector{display:none;height:100%;position:absolute;left:0;top:0;right:0;bottom:0;z-index:1;border-radius:4px}.tabs-content{display:none}.tabs-content.active{display:block}
Extracing TEXT from HTML

As an added option there's also the seventh function called Convert-HTMLToText. It scans the whole HTML and removes all the tags.

$HTMLContentFormatted = @"
<html>
        <!-- HEADER -->
        <head>
                <meta charset="utf-8">
                <meta content="width=device-width, initial-scale=1" name="viewport">
                <meta name="author">
                <meta content="2019-08-09 11:26:37" name="revised">
                <title>My title</title>
                <!-- CSS Default fonts START -->
                <link href="https://fonts.googleapis.com/css?family=Roboto|Hammersmith+One|Questrial|Oswald" type="text/css" rel="stylesheet">
                <!-- CSS Default fonts END -->
                <!-- CSS Default fonts icons START -->
                <link href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" type="text/css" rel="stylesheet">
                <!-- CSS Default fonts icons END -->
        </head>
        <body>
                <div class="flexElement overflowHidden">
                        <table id="DT-hZRTQIVT" class="display compact">
                                <thead>
                                        <tr>
                                                <th>Name</th>
                                                <th class="none">Id</th>
                                                <th class="none">HandleCount</th>
                                                <th>WorkingSet</th>
                                        </tr>
                                </thead>
                                <tbody>
                                        <tr>
                                                <td>1Password</td>
                                                <td>22268</td>
                                                <td>1007</td>
                                                <td>87146496</td>
                                        </tr>
                                        <tr>
                                                <td>aesm_service</td>
                                                <td>25340</td>
                                                <td>189</td>
                                                <td>3948544</td>
                                        </tr>
                                </tbody>
                        </table>
                </div>
                <footer></footer>
                <!-- END FOOTER -->
        </body>
        <!-- END BODY -->
        <!-- FOOTER -->
</html>
"@

Convert-HTMLToText -Content $HTMLContentFormatted

Output

Name Id HandleCount WorkingSet   1Password 22268 1007 87146496  aesm_service 25340 189 3948544

It's not super fancy, but it works. Probably could be replaced with some fancy Regex.

Current Limitations

What you should know when using this module is that for now when you format HTML, it doesn't format inline CSS and JavaScript. It means while you get prettified HTML with tabs and all, but CSS and JavaScript will stay in the same state. I will try to find a way to optimize this either by using a different library or by doing a custom reading and doing some PowerShell magic. We'll see.

Using PSParseHTML as PowerShell Module

For easy use of PSParseHTML, you can install it from PowerShellGallery. Installing it is as easy as it gets.

Install-Module PSParseHTML -AllowClobber -Force

Code as always is stored on GitHub and is free to use and take. After you install it, all commands become available without you having to do anything. Of course, if you prefer to get sources from GitHub be my guest!

Future of PSParseHTML

I may add few more things to this module (not yet sure what) and I do intend to integrate it with PSWriteHTML to either prettify or minify output depending on users needs. Not yet sure at what stage, which features but you can expect it to find it's way in there in one way or another. For now, you can use this in your projects as per your need.

This post was last modified on May 17, 2022 15:17

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Upgrade Azure Active Directory Connect fails with unexpected error

Today, I made the decision to upgrade my test environment and update the version of…

2 weeks ago

Mastering Active Directory Hygiene: Automating Stale Computer Cleanup with CleanupMonster

Have you ever looked at your Active Directory and wondered, "Why do I still have…

4 months ago

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

8 months ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

1 year ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

1 year ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

1 year ago