Created
November 27, 2025 14:59
-
-
Save readingdancer/703bdf1f330ab8239f25c0dc22bc127e to your computer and use it in GitHub Desktop.
A PowerShell file organization script that sorts files into a clean 6-folder structure (Media, Documents, Development, Archives, Applications, Other) with automatic year-based subfolders for images/videos/audio/PDFs and format-based subfolders for images (JPG, PNG, PSD, Vector). Just place in any folder and run!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================ | |
| # TidyFolder.ps1 | |
| # ============================================================================ | |
| # | |
| # Author: Chris Houston (chouston@vizioz.com) | |
| # License: Open Source - MIT License | |
| # Repository: https://github.com/user/TidyFolder (if applicable) | |
| # | |
| # Description: Organizes files into a structured folder hierarchy based on | |
| # file type, with year-based organization for media files. | |
| # The script processes files in the same directory where it is | |
| # located and organizes them into the structure shown below. | |
| # | |
| # ============================================================================ | |
| # FOLDER STRUCTURE | |
| # ============================================================================ | |
| # | |
| # The script creates the following folder hierarchy: | |
| # | |
| # 1 - Media/ | |
| # Images/ | |
| # [Year]/ <- e.g., 2024, 2023 | |
| # JPG/ <- .jpg, .jpeg | |
| # PNG/ <- .png | |
| # PSD/ <- .psd | |
| # Vector/ <- .ai, .eps, .svg | |
| # Other/ <- .gif, .bmp, .tif, .webp, .avif, etc. | |
| # Videos/ | |
| # [Year]/ <- .mov, .mp4, .avi, .mkv, .wmv, etc. | |
| # Audio/ | |
| # [Year]/ <- .wav, .mp3, .flac, .aac, .ogg, etc. | |
| # | |
| # 2 - Documents/ | |
| # PDFs/ | |
| # [Year]/ <- .pdf | |
| # Office/ | |
| # Word/ <- .doc, .docx, .docm, .dot, etc. | |
| # Excel/ <- .xls, .xlsx, .csv, .xlsm, etc. | |
| # PowerPoint/ <- .ppt, .pptx, .pps, .ppsx, etc. | |
| # Text/ <- .txt | |
| # Markdown/ <- .md, .mdc, .rst | |
| # Other/ <- .odt, .rtf, .tex | |
| # | |
| # 3 - Development/ | |
| # Code/ <- .ts, .js, .py, .cs, .html, .css, etc. | |
| # Projects/ <- .csproj, .sln, .vbproj, etc. | |
| # Data/ <- .json, .xml, .yaml, .yml, .ini, etc. | |
| # Patches/ <- .patch, .diff | |
| # Certificates/ <- .crt, .cer, .pem, .pfx, .key | |
| # Minecraft/ <- .mcstructure, .mcworld, .mcpack, etc. | |
| # | |
| # 4 - Archives/ | |
| # [Year]/ <- .zip, .7z, .gz, .rar, .tar, etc. | |
| # | |
| # 5 - Applications/ | |
| # Executables/ <- .exe, .msi, .vsix | |
| # Fonts/ <- .woff, .woff2, .ttf, .otf, .eot | |
| # | |
| # 6 - Other/ | |
| # Folders/ <- Non-numbered folders (moved intact) | |
| # Logs/ <- .log | |
| # | |
| # ============================================================================ | |
| # AUTOMATIC FILE DELETION | |
| # ============================================================================ | |
| # | |
| # The following file types are AUTOMATICALLY DELETED by this script: | |
| # | |
| # .lnk - Windows shortcut files | |
| # .url - Internet shortcut files | |
| # .uda - User-defined application files | |
| # .ics - Calendar event files | |
| # | |
| # These files are typically temporary or easily recreated and are removed | |
| # to reduce clutter. If you need to keep these file types, modify the | |
| # $deleteExtensions variable in the script. | |
| # | |
| # ============================================================================ | |
| # DISCLAIMER | |
| # ============================================================================ | |
| # | |
| # THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| # | |
| # USE AT YOUR OWN RISK. The author shall not be held liable for any damages, | |
| # data loss, or other issues arising from the use of this script. | |
| # | |
| # BEFORE RUNNING THIS SCRIPT: | |
| # 1. Review the code to understand what it does | |
| # 2. Back up any important files | |
| # 3. Test on a small set of files first | |
| # 4. Verify the folder structure meets your needs | |
| # | |
| # By running this script, you acknowledge that you have read and understood | |
| # this disclaimer and accept full responsibility for any consequences. | |
| # | |
| # ============================================================================ | |
| # Force immediate output display | |
| $host.UI.RawUI.WindowTitle = "TidyFolder - Organizing Files..." | |
| [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 | |
| Write-Host "" | |
| Write-Host "============================================" -ForegroundColor Cyan | |
| Write-Host " TidyFolder - File Organization Script" -ForegroundColor Cyan | |
| Write-Host "============================================" -ForegroundColor Cyan | |
| Write-Host "" | |
| # The script will organize files in the same folder where this script is located | |
| $parentDirectory = $PSScriptRoot | |
| Write-Host "Working in: $parentDirectory" -ForegroundColor Yellow | |
| Write-Host "" | |
| # ============================================================================ | |
| # FOLDER STRUCTURE DEFINITION | |
| # ============================================================================ | |
| # Top-level directories | |
| $mediaDirectory = Join-Path -Path $parentDirectory -ChildPath "1 - Media" | |
| $documentsDirectory = Join-Path -Path $parentDirectory -ChildPath "2 - Documents" | |
| $developmentDirectory = Join-Path -Path $parentDirectory -ChildPath "3 - Development" | |
| $archivesDirectory = Join-Path -Path $parentDirectory -ChildPath "4 - Archives" | |
| $applicationsDirectory = Join-Path -Path $parentDirectory -ChildPath "5 - Applications" | |
| $otherDirectory = Join-Path -Path $parentDirectory -ChildPath "6 - Other" | |
| # Media subdirectories | |
| $imagesDirectory = Join-Path -Path $mediaDirectory -ChildPath "Images" | |
| $videosDirectory = Join-Path -Path $mediaDirectory -ChildPath "Videos" | |
| $audioDirectory = Join-Path -Path $mediaDirectory -ChildPath "Audio" | |
| # Documents subdirectories | |
| $pdfsDirectory = Join-Path -Path $documentsDirectory -ChildPath "PDFs" | |
| $officeDirectory = Join-Path -Path $documentsDirectory -ChildPath "Office" | |
| $wordDirectory = Join-Path -Path $officeDirectory -ChildPath "Word" | |
| $excelDirectory = Join-Path -Path $officeDirectory -ChildPath "Excel" | |
| $powerpointDirectory = Join-Path -Path $officeDirectory -ChildPath "PowerPoint" | |
| $textDirectory = Join-Path -Path $documentsDirectory -ChildPath "Text" | |
| $markdownDirectory = Join-Path -Path $documentsDirectory -ChildPath "Markdown" | |
| $otherDocsDirectory = Join-Path -Path $documentsDirectory -ChildPath "Other" | |
| # Development subdirectories | |
| $codeDirectory = Join-Path -Path $developmentDirectory -ChildPath "Code" | |
| $projectsDirectory = Join-Path -Path $developmentDirectory -ChildPath "Projects" | |
| $dataDirectory = Join-Path -Path $developmentDirectory -ChildPath "Data" | |
| $patchesDirectory = Join-Path -Path $developmentDirectory -ChildPath "Patches" | |
| $certificatesDirectory = Join-Path -Path $developmentDirectory -ChildPath "Certificates" | |
| $minecraftDirectory = Join-Path -Path $developmentDirectory -ChildPath "Minecraft" | |
| # Applications subdirectories | |
| $executablesDirectory = Join-Path -Path $applicationsDirectory -ChildPath "Executables" | |
| $fontsDirectory = Join-Path -Path $applicationsDirectory -ChildPath "Fonts" | |
| # Other subdirectories | |
| $foldersDirectory = Join-Path -Path $otherDirectory -ChildPath "Folders" | |
| $logsDirectory = Join-Path -Path $otherDirectory -ChildPath "Logs" | |
| $miscDirectory = Join-Path -Path $otherDirectory -ChildPath "Misc" | |
| # ============================================================================ | |
| # EXTENSION MAPPINGS (using hashtable for O(1) lookup) | |
| # ============================================================================ | |
| Write-Host "Loading extension mappings..." -ForegroundColor Gray | |
| # Image format groups for subfolder organization | |
| $imageFormatMap = @{ | |
| ".jpg" = "JPG"; ".jpeg" = "JPG" | |
| ".png" = "PNG" | |
| ".psd" = "PSD" | |
| ".ai" = "Vector"; ".eps" = "Vector"; ".svg" = "Vector" | |
| ".gif" = "Other"; ".bmp" = "Other"; ".tif" = "Other"; ".tiff" = "Other" | |
| ".webp" = "Other"; ".avif" = "Other"; ".jxl" = "Other"; ".ico" = "Other" | |
| ".heic" = "Other"; ".heif" = "Other"; ".raw" = "Other" | |
| } | |
| # Master extension to destination mapping | |
| # Format: extension = @{ Destination = path; UseYear = $true/$false; UseImageFormat = $true/$false } | |
| $extensionMap = @{} | |
| # Images (with year and format) | |
| @(".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tif", ".tiff", ".svg", ".webp", ".ai", ".psd", ".eps", ".avif", ".jxl", ".ico", ".heic", ".heif", ".raw") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $imagesDirectory; UseYear = $true; UseImageFormat = $true } | |
| } | |
| # Videos (with year) | |
| @(".mov", ".mp4", ".avi", ".mkv", ".wmv", ".flv", ".webm", ".m4v") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $videosDirectory; UseYear = $true; UseImageFormat = $false } | |
| } | |
| # Audio (with year) | |
| @(".wav", ".mp3", ".flac", ".aac", ".ogg", ".wma", ".m4a") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $audioDirectory; UseYear = $true; UseImageFormat = $false } | |
| } | |
| # PDFs (with year) | |
| $extensionMap[".pdf"] = @{ Destination = $pdfsDirectory; UseYear = $true; UseImageFormat = $false } | |
| # Archives (with year) | |
| @(".zip", ".7zip", ".7z", ".gz", ".rar", ".tar", ".bz2") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $archivesDirectory; UseYear = $true; UseImageFormat = $false } | |
| } | |
| # Word | |
| @(".doc", ".docx", ".docm", ".dot", ".dotm", ".dotx") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $wordDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Excel | |
| @(".xlam", ".xls", ".xlsb", ".xlsm", ".xlsx", ".xlt", ".xltm", ".xltx", ".xlw", ".csv") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $excelDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # PowerPoint | |
| @(".pot", ".potm", ".potx", ".ppa", ".ppam", ".pps", ".ppsm", ".ppsx", ".ppt", ".pptm", ".pptx", ".thmx") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $powerpointDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Text | |
| $extensionMap[".txt"] = @{ Destination = $textDirectory; UseYear = $false; UseImageFormat = $false } | |
| # Markdown | |
| @(".md", ".mdc", ".rst") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $markdownDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Other documents | |
| @(".odt", ".rtf", ".tex") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $otherDocsDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Code (excluding .ps1 which needs special handling) | |
| @(".ts", ".js", ".lua", ".bas", ".css", ".bat", ".cs", ".html", ".htm", ".py", ".rb", ".php", ".java", ".c", ".cpp", ".h", ".hpp", ".go", ".rs", ".swift", ".kt", ".scala", ".sh") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $codeDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Special handling for .ps1 - will check filename | |
| $extensionMap[".ps1"] = @{ Destination = $codeDirectory; UseYear = $false; UseImageFormat = $false; ExcludeScript = $true } | |
| # Projects | |
| @(".csproj", ".sln", ".frm", ".frx", ".vbproj", ".fsproj", ".vcxproj", ".xcodeproj") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $projectsDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Data | |
| @(".json", ".xml", ".yaml", ".yml", ".psv", ".toml", ".ini", ".cfg", ".conf") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $dataDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Patches | |
| @(".patch", ".diff") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $patchesDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Certificates | |
| @(".crt", ".cer", ".pem", ".pfx", ".p12", ".key") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $certificatesDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Minecraft | |
| @(".mcstructure", ".mcworld", ".mcpack", ".mcaddon", ".mctemplate") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $minecraftDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Executables | |
| @(".exe", ".msi", ".vsix") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $executablesDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Fonts | |
| @(".woff", ".woff2", ".ttf", ".otf", ".eot") | ForEach-Object { | |
| $extensionMap[$_] = @{ Destination = $fontsDirectory; UseYear = $false; UseImageFormat = $false } | |
| } | |
| # Logs | |
| $extensionMap[".log"] = @{ Destination = $logsDirectory; UseYear = $false; UseImageFormat = $false } | |
| # Extensions to delete | |
| $deleteExtensions = @(".lnk", ".url", ".uda", ".ics") | |
| # ============================================================================ | |
| # HELPER FUNCTIONS | |
| # ============================================================================ | |
| function Ensure-Directory { | |
| param ([string]$path) | |
| if (-not (Test-Path -Path $path -PathType Container)) { | |
| New-Item -Path $path -ItemType Directory | Out-Null | |
| } | |
| } | |
| # ============================================================================ | |
| # MAIN PROCESSING - Single scan approach | |
| # ============================================================================ | |
| Write-Host "Scanning root directory for files..." -ForegroundColor Gray | |
| # Get all files from root directory ONCE | |
| $allFiles = Get-ChildItem -Path $parentDirectory -File -ErrorAction SilentlyContinue | |
| $totalFiles = $allFiles.Count | |
| $processedCount = 0 | |
| $movedCount = 0 | |
| $deletedCount = 0 | |
| $skippedCount = 0 | |
| Write-Host "Found $totalFiles files to process" -ForegroundColor Green | |
| Write-Host "" | |
| Write-Host "Processing files..." -ForegroundColor Cyan | |
| Write-Host "--------------------------------------------" | |
| foreach ($file in $allFiles) { | |
| $processedCount++ | |
| $ext = $file.Extension.ToLower() | |
| # Check if this extension should be deleted | |
| if ($deleteExtensions -contains $ext) { | |
| try { | |
| Remove-Item -Path $file.FullName -Force -ErrorAction Stop | |
| Write-Host " Deleted: $($file.Name)" -ForegroundColor Red | |
| $deletedCount++ | |
| } catch { | |
| Write-Host " Failed to delete: $($file.Name) - $_" -ForegroundColor DarkRed | |
| } | |
| continue | |
| } | |
| # Check if we have a mapping for this extension | |
| if ($extensionMap.ContainsKey($ext)) { | |
| $mapping = $extensionMap[$ext] | |
| # Skip TidyFolder.ps1 | |
| if ($mapping.ExcludeScript -and $file.Name -eq "TidyFolder.ps1") { | |
| $skippedCount++ | |
| continue | |
| } | |
| # Build destination path | |
| $destPath = $mapping.Destination | |
| if ($mapping.UseYear) { | |
| $year = $file.LastWriteTime.Year.ToString() | |
| $destPath = Join-Path -Path $destPath -ChildPath $year | |
| } | |
| if ($mapping.UseImageFormat -and $imageFormatMap.ContainsKey($ext)) { | |
| $formatFolder = $imageFormatMap[$ext] | |
| $destPath = Join-Path -Path $destPath -ChildPath $formatFolder | |
| } | |
| # Ensure destination exists and move file | |
| try { | |
| Ensure-Directory -path $destPath | |
| $destFile = Join-Path -Path $destPath -ChildPath $file.Name | |
| Move-Item -Path $file.FullName -Destination $destFile -Force | |
| Write-Host " Moved: $($file.Name) -> $destPath" -ForegroundColor Green | |
| $movedCount++ | |
| } catch { | |
| Write-Host " Failed to move: $($file.Name) - $_" -ForegroundColor DarkRed | |
| } | |
| } else { | |
| # Unknown extension - skip or move to misc | |
| $skippedCount++ | |
| } | |
| } | |
| Write-Host "" | |
| Write-Host "--------------------------------------------" | |
| Write-Host "Files processed: $processedCount" -ForegroundColor Cyan | |
| Write-Host "Files moved: $movedCount" -ForegroundColor Green | |
| Write-Host "Files deleted: $deletedCount" -ForegroundColor Red | |
| Write-Host "Files skipped: $skippedCount" -ForegroundColor Yellow | |
| Write-Host "" | |
| # ============================================================================ | |
| # PROCESS FOLDERS | |
| # ============================================================================ | |
| Write-Host "Processing folders..." -ForegroundColor Cyan | |
| Write-Host "--------------------------------------------" | |
| $foldersToMove = Get-ChildItem -Path $parentDirectory -Directory | Where-Object { | |
| $_.Name -notmatch '^\d' -and | |
| $_.Name -ne "1 - Media" -and | |
| $_.Name -ne "2 - Documents" -and | |
| $_.Name -ne "3 - Development" -and | |
| $_.Name -ne "4 - Archives" -and | |
| $_.Name -ne "5 - Applications" -and | |
| $_.Name -ne "6 - Other" | |
| } | |
| $foldersMoved = 0 | |
| if ($foldersToMove.Count -gt 0) { | |
| Ensure-Directory -path $foldersDirectory | |
| foreach ($folder in $foldersToMove) { | |
| try { | |
| $destinationPath = Join-Path -Path $foldersDirectory -ChildPath $folder.Name | |
| Move-Item -Path $folder.FullName -Destination $destinationPath -Force | |
| Write-Host " Moved folder: $($folder.Name)" -ForegroundColor Green | |
| $foldersMoved++ | |
| } catch { | |
| Write-Host " Failed to move folder: $($folder.Name) - $_" -ForegroundColor DarkRed | |
| } | |
| } | |
| } | |
| Write-Host "" | |
| Write-Host "Folders moved: $foldersMoved" -ForegroundColor Green | |
| Write-Host "" | |
| # ============================================================================ | |
| # CLEANUP - Delete unwanted files in subfolders | |
| # ============================================================================ | |
| Write-Host "Cleaning up unwanted files in subfolders..." -ForegroundColor Cyan | |
| Write-Host "--------------------------------------------" | |
| $cleanupCount = 0 | |
| $filesToDelete = Get-ChildItem -Path $parentDirectory -File -Recurse -ErrorAction SilentlyContinue | Where-Object { | |
| $deleteExtensions -contains $_.Extension.ToLower() | |
| } | |
| foreach ($file in $filesToDelete) { | |
| try { | |
| Remove-Item -Path $file.FullName -Force -ErrorAction Stop | |
| Write-Host " Deleted: $($file.FullName)" -ForegroundColor Red | |
| $cleanupCount++ | |
| } catch { | |
| Write-Host " Failed to delete: $($file.FullName) - $_" -ForegroundColor DarkRed | |
| } | |
| } | |
| Write-Host "" | |
| Write-Host "Cleanup files deleted: $cleanupCount" -ForegroundColor Red | |
| Write-Host "" | |
| # ============================================================================ | |
| # COMPLETE | |
| # ============================================================================ | |
| Write-Host "============================================" -ForegroundColor Cyan | |
| Write-Host " File organization complete!" -ForegroundColor Cyan | |
| Write-Host "============================================" -ForegroundColor Cyan | |
| Write-Host "" | |
| Read-Host -Prompt "Press Enter to continue..." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment