Skip to content

Instantly share code, notes, and snippets.

@jschlackman
Last active March 6, 2025 22:06
Show Gist options
  • Select an option

  • Save jschlackman/05957a2a769ed17846e8e4c0a0feb23c to your computer and use it in GitHub Desktop.

Select an option

Save jschlackman/05957a2a769ed17846e8e4c0a0feb23c to your computer and use it in GitHub Desktop.
<#
.SYNOPSIS
Outputs an HTML listing of all files in a specified folder, grouped in order by subfolder.
.DESCRIPTION
Enumerates all files and subfolders within a specified path and creates an HTML report listing name, file type, size, created date, and modified date.
Author: James Schlackman <[email protected]>
Last Modified: March 6 2025
.PARAMETER SearchPath
Name of the path to enumerate.
.PARAMETER OutputPath
Pathname of the output file. Defaults to a file in the current directory with the name of the enumerated folder prefixed with the current date in yyMMdd format.
#>
Param(
[Parameter(Mandatory)] [String] $SearchPath,
[Parameter()] [String] $OutputPath = ('{0:yyMMdd} {1}.html' -f (Get-Date), (Get-Item -Path $SearchPath).Name)
)
# Format file sizes with sensible units
Function Format-FileSize() {
Param ([int64]$size)
If ($size -gt 1TB) {'{0:0.00} TB' -f ($size / 1TB)}
ElseIf ($size -gt 1GB) {'{0:0.00} GB' -f ($size / 1GB)}
ElseIf ($size -gt 1MB) {'{0:0.0} MB' -f ($size / 1MB)}
ElseIf ($size -gt 1KB) {'{0:0} KB' -f ($size / 1KB)}
Else {'{0:0} B' -f $size}
}
$emFolder = [Char]::ConvertFromUtf32(0x1F4C2)
$emFile = [Char]::ConvertFromUtf32(0x1F4C4)
$fileTypeCache = @{}
# Get file type description from registry
function Get-FileType() {
param(
[Parameter(Mandatory)] [string] $extension
)
# Check if the file type description is already cached
If (!($fileTypeCache.ContainsKey($extension))) {
# If not, get it from the registry
$typeClass = (Get-ItemProperty -Path ('Registry::HKEY_CLASSES_ROOT\{0}' -f $extension)).'(default)'
$typeDesc = (Get-ItemProperty -Path ('Registry::HKEY_CLASSES_ROOT\{0}' -f $typeClass)).'(default)'
If (!$typeDesc)
{
# Generate a default description if there is no file type association
$typeDesc = '{0} File' -f $extension
}
# Add to the cache
$fileTypeCache.Add($extension, $typeDesc)
}
# Return the description from the cache
$fileTypeCache[$extension]
}
# Get HTML representation of file listing for a single folder
function Export-HtmlFileList() {
param(
[Parameter(Mandatory)] [string] $DirectoryPath
)
$folderName = $DirectoryPath
If ($folderName -eq $rootFolder.FullName) {
$folderName = '(Root)'
} Else {
$folderName = $folderName.Substring($rootFolder.FullName.Length + 1)
}
# Return folder name heading
'<h2 id="{0}">{1} {2}</h2>' -f $DirectoryPath.GetHashCode(), $emFolder, $folderName
# Get listings of files in this folder and dirext subfolders
$subFolders = $allFolders | Where-Object {$_.Parent.FullName -eq $DirectoryPath}
$subFiles = $allFiles | Where-Object {$_.DirectoryName -eq $DirectoryPath}
# If the folder is not empty
If ([bool]$subFolders -or [bool]$subFiles) {
# Return a list of links to subfolders
If ([bool]$subFolders) {
'<p>Subfolders: {0}<ul class="folders">' -f $($subFolders).Count
"`n"
$subFolders | ForEach-Object {
'<li><a href=#{0}>{1}</a></li>' -f $_.FullName.GetHashCode(), $_.Name
"`n"
}
"</ul></p>`n"
}
If ([bool]$subFiles) {
$fileProperties = `
@{Name='File Name';Expression={'{0} {1}' -f $emFile, $_.BaseName}},
@{Name='File Type';Expression={Get-FileType($_.Extension)}},
@{Name='Size';Expression={Format-FileSize $_.Length}},
@{Name='Created';Expression={Get-Date -UFormat '%D' $_.CreationTime}},
@{Name='Modified';Expression={Get-Date -UFormat '%D' $_.LastWriteTime}}
# Return file listing summary
'<p>Files: {0}</br>Total size: {1}</p>' -f @($subFiles).Count, (Format-FileSize (@($subFiles) | Measure-Object -Property Length -Sum).Sum)
# Return file listing table
$subFiles | Select $fileProperties | ConvertTo-Html -Fragment
}
} Else {'<p><em>Empty folder</em></p>'}
}
If (!(Test-Path $SearchPath)) {
Write-Host "Folder not found: $SearchPath" -ForegroundColor Red
} Else {
$rootFolder = Get-Item -Path $SearchPath
# Search the entire specified folder
$searchResults = Get-ChildItem -Path $rootFolder.FullName -Recurse
# Get file listing for totals
$allFiles = $searchResults | Where-Object {!($_.Attributes -band [System.IO.FileAttributes]'Directory')}
# Get all subfolders
$allFolders = $searchResults | Where-Object {$_.Attributes -band [System.IO.FileAttributes]'Directory'}
# Build report header and intro
$reportHead = @"
<head><style>
body {font-family: Calibri,sans-serif; font-size: 11pt}
h1 {font-family: Segoe UI Light,sans-serif}
p.footer {margin-top: 3em; font-size: 9pt; font-style: italic; color: gray}
table {background-color: #e6e6e6}
td,th {background-color: white; padding: 3px}
th {background-color: #f2f2f2}
ul.folders {list-style: none; padding: 0; margin: 0;}
ul.folders li {padding-left: 1rem; text-indent: -0.7rem}
ul.folders li::before {content: "$($emFolder) "}
</style></head>
"@
$reportIntro = '<h1>Report: {0}</h1><p><p><ul><li>Total files found: {1}</li><li>Subfolders: {2}</li><li>Total size: {3}</li></ul></p>' -f
$rootFolder.Name,
$allFiles.Count.ToString('N0'),
$allFolders.Count.ToString('N0'),
(Format-FileSize ($allFiles | Measure-Object -Property Length -Sum).Sum)
# Get the file listing for the root folder
$reportBody = Export-HtmlFileList -DirectoryPath $rootFolder.FullName
# Append the file listings for each subfolder
$allFolders | Sort-Object -Property FullName | ForEach-Object {
$reportBody += Export-HtmlFileList -DirectoryPath $_.FullName
}
# Output the report to disk
"<html>$reportHead`n<body>$reportIntro`n$reportBody</body></html>" | Out-File -FilePath $OutputPath -Encoding UTF8
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment