<#
Nama Script : EmptyIndexDestroyer.ps1
Deskripsi :
Script ini mencari dan menghapus file index.md yang memiliki konten kosong atau
hanya berisi frontmatter kosong dari direktori yang dipilih user, dengan opsi
kedalaman folder.
Alur User :
1. User memasukkan path folder target.
2. Script validasi path, jika tidak ada minta ulang.
3. User bisa ganti nama file target (default: index.md) atau Enter untuk pakai default.
4. User pilih scope:
- Y : Semua subfolder (rekursif penuh)
- N : Batal, kembali ke input path
- Angka : Sampai level kedalaman tertentu
5. Script preview dulu file empty yang akan di-delete (dry run tampilan).
6. User konfirmasi Y/N sebelum eksekusi.
7. Script hapus file dan tampilkan summary.
File dianggap EMPTY jika:
- File size 0 byte
- Hanya berisi whitespace
- Hanya berisi frontmatter kosong (---, ---)
- Hanya berisi frontmatter dengan whitespace/newlines saja
#>
# ── Shell.Application untuk Recycle Bin ───────────────────────────────────────
$shell = New-Object -ComObject Shell.Application
function Move-ToRecycleBin {
param ([string]$FilePath)
try {
$item = $shell.Namespace(0).ParseName((Resolve-Path $FilePath).Path)
if ($item) {
$item.InvokeVerb("delete")
return $true
}
return $false
}
catch {
return $false
}
}
# ── Check if file is empty (including empty frontmatter) ──────────────────────
function Test-EmptyIndexFile {
param ([string]$FilePath)
try {
# Check file size
$fileInfo = Get-Item $FilePath -ErrorAction Stop
if ($fileInfo.Length -eq 0) {
return $true
}
# Read content
$content = Get-Content $FilePath -Raw -ErrorAction Stop
# Check if only whitespace
if ([string]::IsNullOrWhiteSpace($content)) {
return $true
}
# Check for empty frontmatter pattern (---, ---)
# Pattern: starts with ---, then only whitespace/newlines, then ---
if ($content -match '^\s*---\s*---\s*$') {
return $true
}
# Pattern: starts with ---, then only whitespace, then ---, then only whitespace
if ($content -match '^\s*---[\s\n]*---\s*$') {
return $true
}
# Pattern: frontmatter block only (---, content with only whitespace, ---)
$lines = $content -split "`n"
if ($lines.Count -ge 2 -and $lines[0].Trim() -eq '---') {
# Find closing ---
$closingIndex = -1
for ($i = 1; $i -lt $lines.Count; $i++) {
if ($lines[$i].Trim() -eq '---') {
$closingIndex = $i
break
}
}
# If closing --- found
if ($closingIndex -gt 0) {
# Check if frontmatter content is only whitespace
$frontmatterContent = $lines[1..$($closingIndex - 1)] -join "`n"
# Check if anything after closing ---
if ($closingIndex -eq $lines.Count - 1) {
# Nothing after ---, check if frontmatter is empty
if ([string]::IsNullOrWhiteSpace($frontmatterContent)) {
return $true
}
} else {
# Something after closing ---, check if it's only whitespace
$afterFrontmatter = $lines[($closingIndex + 1)..$($lines.Count - 1)] -join "`n"
if ([string]::IsNullOrWhiteSpace($frontmatterContent) -and [string]::IsNullOrWhiteSpace($afterFrontmatter)) {
return $true
}
}
}
}
return $false
}
catch {
return $false
}
}
# ── Collect empty files sampai level tertentu ─────────────────────────────────
function Get-EmptyTargetFiles {
param (
[string]$Path,
[string]$FileName,
[int]$Level = 0,
[int]$MaxLevel = -1 # -1 = unlimited
)
$results = @()
# Cek file di level ini
$filePath = Join-Path $Path $FileName
if ((Test-Path $filePath -PathType Leaf) -and (Test-EmptyIndexFile $filePath)) {
$results += $filePath
}
# Stop kalau sudah capai max level
if ($MaxLevel -ne -1 -and $Level -ge $MaxLevel) {
return $results
}
# Rekursi ke subfolder
$subFolders = Get-ChildItem -Path $Path -Directory -ErrorAction SilentlyContinue | Sort-Object Name
foreach ($folder in $subFolders) {
$results += Get-EmptyTargetFiles -Path $folder.FullName -FileName $FileName -Level ($Level + 1) -MaxLevel $MaxLevel
}
return $results
}
# ── Print tree preview dengan highlight file target yang empty ────────────────
function Print-PreviewTree {
param (
[string]$Path,
[string]$FileName,
[int]$Level = 0,
[int]$MaxLevel = -1,
[ref]$FoundCount
)
if ($MaxLevel -ne -1 -and $Level -ge $MaxLevel) { return }
$indent = " " * $Level
# Cek apakah ada file target EMPTY di folder ini
$filePath = Join-Path $Path $FileName
if ((Test-Path $filePath -PathType Leaf) -and (Test-EmptyIndexFile $filePath)) {
$fileSize = (Get-Item $filePath).Length
Write-Host "$indent 🗑️ $FileName" -ForegroundColor Red -NoNewline
Write-Host " (${fileSize} bytes)" -ForegroundColor DarkRed
$FoundCount.Value++
}
$subFolders = Get-ChildItem -Path $Path -Directory -ErrorAction SilentlyContinue | Sort-Object Name
foreach ($folder in $subFolders) {
Write-Host "$indent 📁 $($folder.Name)" -ForegroundColor DarkGray
Print-PreviewTree -Path $folder.FullName -FileName $FileName -Level ($Level + 1) -MaxLevel $MaxLevel -FoundCount $FoundCount
}
}
# ── Banner ─────────────────────────────────────────────────────────────────────
Clear-Host
Write-Host "╔══════════════════════════════════════════════╗" -ForegroundColor DarkRed
Write-Host "║ 🗑️ EMPTY INDEX DESTROYER v1.0 ║" -ForegroundColor DarkRed
Write-Host "║ Delete empty index.md files by depth level ║" -ForegroundColor DarkRed
Write-Host "╚══════════════════════════════════════════════╝" -ForegroundColor DarkRed
Write-Host ""
# ── MAIN LOOP ─────────────────────────────────────────────────────────────────
do {
# ── Step 1: Input path ────────────────────────────────────────────────────
do {
$targetFolder = Read-Host "📂 Masukkan path folder target (contoh: D:\MyProject)"
$targetFolder = $targetFolder.Trim('"').Trim("'").Trim()
if (-not (Test-Path $targetFolder -PathType Container)) {
Write-Host "`n❌ Path tidak ditemukan atau bukan folder. Periksa kembali!`n" -ForegroundColor Red
$validPath = $false
} else {
$validPath = $true
}
} while (-not $validPath)
# ── Step 2: Input nama file target ────────────────────────────────────────
Write-Host ""
$inputFileName = Read-Host "📄 Nama file target (Enter = pakai default 'index.md')"
$inputFileName = $inputFileName.Trim()
if ([string]::IsNullOrWhiteSpace($inputFileName)) {
$targetFileName = "index.md"
} else {
$targetFileName = $inputFileName
}
Write-Host " → Target file: " -NoNewline
Write-Host $targetFileName -ForegroundColor Cyan
# ── Step 3: Pilih scope / kedalaman ───────────────────────────────────────
$restartToPath = $false
do {
Write-Host ""
$inputLevel = Read-Host "🔍 Scope pencarian? (Y = semua subfolder, N = batal, atau angka level kedalaman)"
$inputLevel = $inputLevel.Trim().ToLower()
if ($inputLevel -eq 'n') {
Write-Host "`n⚠️ Dibatalkan. Kembali ke pemilihan folder...`n" -ForegroundColor Yellow
$restartToPath = $true
break
}
elseif ($inputLevel -eq 'y') {
$maxLevel = -1
$scopeLabel = "semua subfolder (rekursif penuh)"
$validScope = $true
}
elseif ([int]::TryParse($inputLevel, [ref]$null)) {
$maxLevel = [int]$inputLevel
if ($maxLevel -le 0) {
Write-Host "⚠️ Masukkan angka lebih besar dari 0." -ForegroundColor Yellow
$validScope = $false
} else {
$scopeLabel = "kedalaman level $maxLevel"
$validScope = $true
}
}
else {
Write-Host "❌ Input tidak valid. Masukkan 'Y', 'N', atau angka." -ForegroundColor Red
$validScope = $false
}
} while (-not $validScope -and -not $restartToPath)
if ($restartToPath) { continue }
# ── Step 4: Preview / Dry Run ─────────────────────────────────────────────
Write-Host ""
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray
Write-Host "🔎 PREVIEW — File EMPTY yang akan dihapus:" -ForegroundColor Yellow
Write-Host " Folder : $targetFolder" -ForegroundColor Gray
Write-Host " File : $targetFileName" -ForegroundColor Gray
Write-Host " Scope : $scopeLabel" -ForegroundColor Gray
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray
Write-Host ""
$foundCount = [ref]0
Write-Host "📁 $targetFolder" -ForegroundColor White
Print-PreviewTree -Path $targetFolder -FileName $targetFileName -MaxLevel $maxLevel -FoundCount $foundCount
Write-Host ""
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray
if ($foundCount.Value -eq 0) {
Write-Host "✅ Tidak ada file '$targetFileName' kosong yang ditemukan dalam scope ini." -ForegroundColor Green
Write-Host ""
$retry = Read-Host "🔁 Coba folder/file lain? (Y = ya, N = keluar)"
if ($retry.Trim().ToLower() -ne 'y') {
break
} else {
continue
}
}
Write-Host "📊 Total ditemukan: " -NoNewline
Write-Host "$($foundCount.Value) file EMPTY" -ForegroundColor Red -NoNewline
Write-Host " akan dihapus."
Write-Host ""
# ── Step 5: Konfirmasi eksekusi ───────────────────────────────────────────
$confirm = Read-Host "⚠️ Lanjutkan? File akan dihapus ke Recycle Bin (Y/N)"
$confirm = $confirm.Trim().ToLower()
if ($confirm -ne 'y') {
Write-Host "`n❌ Dibatalkan. Tidak ada file yang dihapus.`n" -ForegroundColor Yellow
$retry = Read-Host "🔁 Coba lagi? (Y = ya, N = keluar)"
if ($retry.Trim().ToLower() -ne 'y') { break } else { continue }
}
# ── Step 6: Eksekusi ──────────────────────────────────────────────────────
Write-Host ""
Write-Host "🚀 Menghapus file empty..." -ForegroundColor Cyan
$filesToDelete = Get-EmptyTargetFiles -Path $targetFolder -FileName $targetFileName -MaxLevel $maxLevel
$successCount = 0
$failCount = 0
$failedFiles = @()
foreach ($file in $filesToDelete) {
try {
if (Move-ToRecycleBin $file) {
Write-Host " ✅ Deleted → $file" -ForegroundColor Green
$successCount++
} else {
throw "Failed to move to recycle bin"
}
}
catch {
Write-Host " ❌ Gagal → $file" -ForegroundColor Red
$failCount++
$failedFiles += $file
}
}
# ── Step 7: Summary ───────────────────────────────────────────────────────
Write-Host ""
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray
Write-Host "📊 SUMMARY" -ForegroundColor White
Write-Host " ✅ Berhasil dihapus : $successCount file" -ForegroundColor Green
if ($failCount -gt 0) {
Write-Host " ❌ Gagal : $failCount file" -ForegroundColor Red
foreach ($f in $failedFiles) {
Write-Host " - $f" -ForegroundColor DarkRed
}
}
Write-Host " 🗑️ Semua file ada di Recycle Bin — bisa di-restore kalau perlu." -ForegroundColor DarkGray
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray
Write-Host ""
# ── Selesai / ulangi ──────────────────────────────────────────────────────
$again = Read-Host "🔁 Destroy folder/file lain? (Y = ya, N = keluar)"
if ($again.Trim().ToLower() -ne 'y') { break }
} while ($true)
Write-Host ""
Write-Host "👋 EmptyIndexDestroyer selesai. Sampai jumpa!" -ForegroundColor DarkCyan
Write-Host ""