3 min read

📂 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 Logs folder 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 }