📂 Organize Downloads (PowerShell 7.5)
A robust utility script designed to declutter your Downloads folder by automatically sorting files into categorized subdirectories. Updated for PowerShell 7.5.
📋 Overview
This script scans a target directory (defaulting to your user Downloads folder) and moves files into an Organized subfolder, sorting them into categories like Documents, Images, Dev-Scripts, and Installers.
It includes safety features like a Dry Run mode to preview changes and a Types Scanner to analyze the contents before cleaning.
🚀 Usage
1. Standard Cleanup
Organizes the default Downloads folder immediately.
.\Organize-Downloads.ps1
2. Dry Run (Safety Mode)
Simulates the organization process without moving any files. It creates a log file showing exactly what would happen and opens it automatically.
.\Organize-Downloads.ps1 -Dry
3. Analyze File Types
Scans the folder and displays a table of file extensions and their counts. Does not move any files.
.\Organize-Downloads.ps1 -Types
4. Custom Path
Organize a specific folder other than Downloads.
.\Organize-Downloads.ps1 -Path "C:\Users\Name\Desktop\MessyFolder"
⚙️ Categories
The script sorts files into the following subdirectories inside \Organized:
| Category | Extensions |
|---|---|
| Documents | .pdf, .docx, .txt, .xlsx, .pptx, .csv, .eml |
| Images | .jpg, .png, .gif, .webp, .svg, .avif |
| Design | .psd, .ai, .excalidraw |
| Dev-Web | .php, .js, .json, .html, .css, .xml |
| Dev-Scripts | .ps1, .py, .md, .log |
| Installers | .exe, .msi, .sys, .cab |
| Archives | .zip, .rar, .7z, .gz |
| Media | .mp3, .wav, .mp4, .mkv, .mov |
| Other | Anything not matching the above |
📝 Logging
- Logs are saved to a
Logsfolder in the parent directory of the target (e.g.,C:\Users\You\Logs). - Log filenames follow the format:
ManualRunLog_yyyy-MM-dd.txt.
⚠️ Requirements
- PowerShell 7.5 or later.
[CmdletBinding()]
param (
[switch]$Dry,
[switch]$Types,
[string]$Path = "$env:USERPROFILE\Downloads"
)
# --- Configuration ---
$DownloadsFolder = $Path
$Files = Get-ChildItem -Path $DownloadsFolder -File
# --- New Functionality: Types Scanner ---
if ($Types) {
Write-Host "Scanning file types in: $DownloadsFolder" -ForegroundColor Yellow
# Group files by extension and sort by count
$ExtensionGroups = $Files | Group-Object Extension | Sort-Object Count -Descending
if ($ExtensionGroups.Count -eq 0) {
Write-Host "No files found in the directory." -ForegroundColor Gray
} else {
$ExtensionGroups | Select-Object @{Name="Extension"; Expression={$_.Name}}, Count | Format-Table -AutoSize
}
exit # Stop the script here so it doesn't proceed to organize
}
$OrganizedFolder = Join-Path $DownloadsFolder "Organized"
$LogsFolder = Join-Path (Split-Path $DownloadsFolder) "Logs"
$LogFile = Join-Path $LogsFolder ("ManualRunLog_" + (Get-Date -format "yyyy-MM-dd") + ".txt")
$SubFolders = [ordered]@{
"Documents" = ".pdf", ".docx", ".txt", ".xlsx", ".pptx", ".doc", ".ppt", ".csv", ".eml"
"Images" = ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".ico", ".avif"
"Design" = ".psd", ".ai", ".excalidraw"
"Dev-Web" = ".php", ".js", ".json", ".html", ".htm", ".css", ".xml", ".woff2"
"Dev-Scripts" = ".ps1", ".py", ".md", ".log"
"Installers" = ".exe", ".msi", ".sys", ".cab"
"Archives" = ".zip", ".rar", ".7z", ".gz", ".xz", ".wpress"
"Music" = ".mp3", ".wav", ".flac"
"Videos" = ".mp4", ".avi", ".mov", ".mkv"
"Other" = "*"
}
# --- Preparation ---
if (!(Test-Path $OrganizedFolder)) {
Write-Verbose "Creating organized folder: $OrganizedFolder"
New-Item -ItemType Directory -Path $OrganizedFolder | Out-Null
}
if (!(Test-Path $LogsFolder)) {
New-Item -ItemType Directory -Path $LogsFolder | Out-Null
}
# --- Core Logic ---
Write-Host "Starting manual organization of: $DownloadsFolder" -ForegroundColor Yellow
if ($Dry) { Write-Host "!!! DRY RUN ENABLED - No files will be moved !!!" -ForegroundColor Magent }
foreach ($File in $Files) {
foreach ($Category in $SubFolders.Keys) {
$Extensions = $SubFolders[$Category]
# Check for extension match or the catch-all wildcard
if ($Extensions -contains $File.Extension -or $Extensions -contains "*") {
$DestinationFolder = Join-Path $OrganizedFolder $Category
$DestinationPath = Join-Path $DestinationFolder $File.Name
if (!(Test-Path $DestinationFolder)) {
New-Item -ItemType Directory -Path $DestinationFolder | Out-Null
}
$Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
if ($Dry) {
$LogMsg = "$Timestamp - [DRY RUN] Would move '$($File.Name)' to '$Category'"
Write-Host $LogMsg -ForegroundColor Gray
$LogMsg | Out-File -Append -FilePath $LogFile
} else {
$LogMsg = "$Timestamp - Moved '$($File.Name)' to '$Category'"
try {
Move-Item -Path $File.FullName -Destination $DestinationPath -Force
Write-Host $LogMsg -ForegroundColor Cyan
$LogMsg | Out-File -Append -FilePath $LogFile
} catch {
$ErrorMsg = "$Timestamp - ERROR: Could not move '$($File.Name)': $($_.Exception.Message)"
Write-Host $ErrorMsg -ForegroundColor Red
$ErrorMsg | Out-File -Append -FilePath $LogFile
}
}
# This break exits the $Category loop and moves to the next $File
break
}
}
}
Write-Host "Organization complete." -ForegroundColor Green
if ($Dry) { Invoke-Item $LogFile }