Powershell Website from folder structure

When I’m coding, I always find myself using a base directory that I then create additional sub folders in and I put my code in there so it’s organize by the name of the folder.

This is great on your local on your device but not so great when you’re trying to illustrate this in many of my blog posts like I do on here so I’ve come up with some groovy Powershell that will take the folder structure, and the files in that folder, structure, and create a website from it, which mirrors that folder structure.

When you run the script you need to specify an output folder for the HTML, This will actually copy the folder structure and the contents, which includes all your scripts to that folder so you can then upload that To your desired website hosting 

Script also add icons to certain file types which could be customized in the script to include what you wish, Obviously, if you’re developing version of your internal script, you would need to change all the companies specific information before you upload it. otherwise, that will be available.

Below is the local folder structure:


This is then that folder structure using the website:


Then one of the subfolders looks like this, as you can see the icon next to the Powershell script:


You will need to run the script with the command below as it requires certain syntax, you need to update the variables in bold:

.\WebGenerator.ps1 -SourceDirectory "<source_folder>" -OutputDirectory "<output_directory>"

Script : WebGenerator.ps1

# Parameters
param(
    [Parameter(Mandatory=$true)]
    [string]$SourceDirectory,
    [Parameter(Mandatory=$true)]
    [string]$OutputDirectory
)

# Create output directory if it doesn't exist
New-Item -ItemType Directory -Force -Path $OutputDirectory | Out-Null

# Function to convert special characters to HTML entities
function Convert-ToHtmlEncoded {
    param([string]$text)
    $text = $text.Replace("&", "&amp;")
    $text = $text.Replace("<", "&lt;")
    $text = $text.Replace(">", "&gt;")
    $text = $text.Replace('"', "&quot;")
    $text = $text.Replace("'", "&#39;")
    return $text
}

# Function to convert file content to HTML-safe format
function Convert-ToHtmlSafe {
    param(
        [string]$content,
        [string]$extension
    )
    
    if ($extension -eq ".ps1") {
        # Special handling for PowerShell files
        $lines = $content -split "`n"
        $htmlContent = ""
        
        foreach ($line in $lines) {
            # Basic PowerShell syntax highlighting
            $line = Convert-ToHtmlEncoded $line
            
            # Keywords
            $line = $line -replace "\b(function|param|if|else|foreach|while|switch|return|break|continue|try|catch|finally|class|using|throw)\b", '<span class="keyword">$1</span>'
            
            # Cmdlets (common ones)
            $line = $line -replace "\b(Get-|Set-|New-|Remove-|Write-|Read-|Import-|Export-)\w+\b", '<span class="cmdlet">$0</span>'
            
            # Variables
            $line = $line -replace "(\$\w+)", '<span class="variable">$1</span>'
            
            # Comments
            $line = $line -replace "(#.*$)", '<span class="comment">$1</span>'
            
            # Strings (basic handling)
            $line = $line -replace '(".*?")', '<span class="string">$1</span>'
            $line = $line -replace "('.*?')", '<span class="string">$1</span>'
            
            $htmlContent += $line + "<br/>"
        }
        return $htmlContent
    }
    else {
        # Standard HTML encoding for other files
        return (Convert-ToHtmlEncoded $content) -replace "`n", "<br/>"
    }
}

# Create styles.css
$css = @"
:root {
    --background: #ffffff;
    --text: #333333;
    --hover: #f0f0f0;
    --border: #e0e0e0;
    --link: #0066cc;
    --link-visited: #551a8b;
    --code-bg: #f8f8f8;
}

@media (prefers-color-scheme: dark) {
    :root {
        --background: #1a1a1a;
        --text: #ffffff;
        --hover: #2a2a2a;
        --border: #333333;
        --link: #66b3ff;
        --link-visited: #b088ff;
        --code-bg: #2d2d2d;
    }
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', system-ui, sans-serif;
    background: var(--background);
    color: var(--text);
    line-height: 1.6;
    padding: 20px;
}

.container {
    max-width: 1200px;
    margin: 0 auto;
}

.breadcrumb {
    padding: 10px;
    margin-bottom: 20px;
    border-bottom: 1px solid var(--border);
}

.breadcrumb a {
    color: var(--link);
    text-decoration: none;
}

.breadcrumb a:visited {
    color: var(--link-visited);
}

.directory-list {
    list-style: none;
}

.directory-item {
    padding: 10px;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
}

.directory-item:hover {
    background: var(--hover);
}

.directory-item a {
    color: var(--link);
    text-decoration: none;
    display: flex;
    align-items: center;
    width: 100%;
}

.directory-item a:visited {
    color: var(--link-visited);
}

.file-icon {
    margin-right: 10px;
    font-size: 1.2em;
}

.folder-icon {
    color: #ffd700;
}

.file-content {
    margin-top: 20px;
    padding: 20px;
    background: var(--code-bg);
    border: 1px solid var(--border);
    border-radius: 4px;
    white-space: pre-wrap;
    font-family: 'Cascadia Code', 'Consolas', monospace;
    line-height: 1.5;
}

.back-link {
    display: inline-block;
    margin-bottom: 20px;
    color: var(--link);
    text-decoration: none;
}

.file-name {
    margin-bottom: 15px;
    font-size: 1.2em;
    font-weight: bold;
}

/* PowerShell Syntax Highlighting */
.keyword { color: #569CD6; }
.cmdlet { color: #4EC9B0; }
.variable { color: #9CDCFE; }
.string { color: #CE9178; }
.comment { color: #6A9955; font-style: italic; }
.parameter { color: #9CDCFE; }

@media (prefers-color-scheme: light) {
    .keyword { color: #0000FF; }
    .cmdlet { color: #008080; }
    .variable { color: #FF4500; }
    .string { color: #A31515; }
    .comment { color: #008000; }
    .parameter { color: #2B91AF; }
}
"@

$css | Out-File -FilePath (Join-Path $OutputDirectory "styles.css") -Encoding UTF8

# Function to get file icon based on extension
function Get-FileIcon {
    param([string]$extension)
    switch ($extension.ToLower()) {
        ".txt"  { return "📄" }
        ".pdf"  { return "📕" }
        ".doc"  { return "📘" }
        ".docx" { return "📘" }
        ".xls"  { return "📗" }
        ".xlsx" { return "📗" }
        ".jpg"  { return "🖼️" }
        ".jpeg" { return "🖼️" }
        ".png"  { return "🖼️" }
        ".gif"  { return "🖼️" }
        ".ps1"  { return "⚡" }
        default { return "📄" }
    }
}

# Function to generate breadcrumb navigation
function Get-Breadcrumb {
    param(
        [string]$relativePath,
        [int]$currentDepth
    )
    
    $upPath = "../" * $currentDepth
    $breadcrumb = "<a href=`"$($upPath)index.html`">Home</a>"
    
    if ($relativePath) {
        $parts = $relativePath -split '\\'
        $currentPath = ""
        $upCount = $currentDepth
        
        foreach ($part in $parts) {
            $currentPath += $part + "\"
            $breadcrumb += " / "
            if ($part -eq $parts[-1]) {
                $breadcrumb += $part
            }
            else {
                $upPathPart = "../" * $upCount
                $trimmedPath = $currentPath.TrimEnd('\')
                $breadcrumb += "<a href=`"$($upPathPart)$($trimmedPath)/index.html`">$part</a>"
                $upCount--
            }
        }
    }
    
    return $breadcrumb
}

# Function to create a unique filename for file content pages
function Get-SafeFileName {
    param([string]$fileName)
    return ($fileName -replace '[^\w\-\.]', '_')
}

# Function to process directory and generate HTML
function Process-Directory {
    param(
        [string]$currentPath,
        [string]$relativePath = "",
        [int]$depth = 0
    )

    $items = Get-ChildItem -Path $currentPath
    $breadcrumb = Get-Breadcrumb -relativePath $relativePath -currentDepth $depth
    $upPath = "../" * $depth

    # Create the HTML header
    $htmlHeader = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Directory Browser - $relativePath</title>
    <link rel="stylesheet" href="$($upPath)styles.css">
</head>
<body>
    <div class="container">
        <div class="breadcrumb">
            📁 $breadcrumb
        </div>
        <ul class="directory-list">
"@

    $htmlContent = $htmlHeader

    foreach ($item in $items) {
        $itemRelativePath = if ($relativePath) {
            Join-Path $relativePath $item.Name
        }
        else {
            $item.Name
        }

        if ($item.PSIsContainer) {
            # Create subdirectory and its index
            $subDirPath = Join-Path $OutputDirectory $itemRelativePath
            New-Item -ItemType Directory -Force -Path $subDirPath | Out-Null
            Process-Directory -currentPath $item.FullName -relativePath $itemRelativePath -depth ($depth + 1)

            $htmlContent += @"
            <li class="directory-item">
                <a href="$($item.Name)/index.html">
                    <span class="file-icon folder-icon">📁</span>
                    $($item.Name)
                </a>
            </li>
"@
        }
        else {
            $fileContent = Get-Content -Path $item.FullName -Raw -ErrorAction SilentlyContinue
            if ($fileContent) {
                $safeContent = Convert-ToHtmlSafe -content $fileContent -extension $item.Extension
                $fileIcon = Get-FileIcon $item.Extension
                $safeFileName = Get-SafeFileName -fileName $item.Name
                $filePath = Join-Path (Join-Path $OutputDirectory $relativePath) ($safeFileName + ".html")

                # Create file content HTML
                $fileHtml = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>$($item.Name)</title>
    <link rel="stylesheet" href="$($upPath)styles.css">
</head>
<body>
    <div class="container">
        <a href="index.html" class="back-link">← Back to directory</a>
        <div class="file-name">$($item.Name)</div>
        <div class="file-content">$safeContent</div>
    </div>
</body>
</html>
"@
                $fileHtml | Out-File -FilePath $filePath -Encoding UTF8

                $htmlContent += @"
                <li class="directory-item">
                    <a href="$safeFileName.html">
                        <span class="file-icon">$fileIcon</span>
                        $($item.Name)
                    </a>
                </li>
"@
            }
        }
    }

    $htmlFooter = @"
        </ul>
    </div>
</body>
</html>
"@

    $htmlContent += $htmlFooter
    
    $outputPath = if ($relativePath) {
        Join-Path (Join-Path $OutputDirectory $relativePath) "index.html"
    }
    else {
        Join-Path $OutputDirectory "index.html"
    }

    $htmlContent | Out-File -FilePath $outputPath -Encoding UTF8
}

# Start processing from the root directory
Process-Directory -currentPath $SourceDirectory

Previous Post Next Post

نموذج الاتصال