AutomateMyJob
Back to BlogPowerShell

PowerShell File Organization: Never Manually Sort Files Again

Chris Anderson12 min read

PowerShell File Organization: Never Manually Sort Files Again

You know the feeling. You open your Downloads folder and it's chaos—PDFs mixed with images, installers next to spreadsheets, screenshots from three months ago buried under today's files. You could spend an hour sorting everything, but you never do. The pile just keeps growing.

Today, we're building a PowerShell script that automatically organizes files into logical folders. Run it once, schedule it, and never think about file organization again.

What You'll Learn

  • How to identify files by extension, date, and name patterns
  • Building flexible organization rules that adapt to your needs
  • Creating safe file operations with conflict handling
  • Implementing dry-run mode to preview changes
  • Scheduling automatic organization

Prerequisites

  • Windows 10/11 with PowerShell 5.1+
  • Basic understanding of file paths
  • A messy folder that needs organizing (we all have one)

The Manual Pain

Manual file organization looks like this:

  1. Open the cluttered folder
  2. Sort by type... no wait, by date... actually by name
  3. Select similar files, create a new folder, drag them in
  4. Repeat for every file type
  5. Deal with duplicates manually
  6. Give up halfway through because a meeting started
  7. Come back in two weeks to an even bigger mess

This isn't just tedious—it's a recurring problem that never stays solved. Let's automate it permanently.

The Automated Solution

Our script will:

  1. Scan a source folder for all files
  2. Categorize files based on configurable rules
  3. Create destination folders automatically
  4. Move files while handling naming conflicts
  5. Log all operations for review
  6. Support dry-run mode for safe previewing

Step 1: Define Organization Rules

First, let's create a flexible rule system that maps file extensions to folder names:

powershell
1# File organization rules - customize these!
2$OrganizationRules = @{
3    # Documents
4    "Documents" = @(".pdf", ".doc", ".docx", ".txt", ".rtf", ".odt", ".xls", ".xlsx", ".ppt", ".pptx")
5    
6    # Images
7    "Images" = @(".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp", ".ico", ".tiff")
8    
9    # Videos
10    "Videos" = @(".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".m4v")
11    
12    # Audio
13    "Audio" = @(".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma", ".m4a")
14    
15    # Archives
16    "Archives" = @(".zip", ".rar", ".7z", ".tar", ".gz", ".bz2")
17    
18    # Installers
19    "Installers" = @(".exe", ".msi", ".msix", ".appx")
20    
21    # Code
22    "Code" = @(".ps1", ".py", ".js", ".html", ".css", ".json", ".xml", ".yml", ".yaml", ".sh", ".bat", ".cmd")
23    
24    # Data
25    "Data" = @(".csv", ".sql", ".db", ".sqlite", ".json", ".xml")
26}

This approach is powerful because you can easily add new categories or file types. Want a "Screenshots" folder? Add a rule for files matching a pattern.

Step 2: Create the File Categorization Function

Now let's write a function that determines where each file should go:

powershell
1function Get-FileCategory {
2    param (
3        [Parameter(Mandatory = $true)]
4        [System.IO.FileInfo]$File,
5        
6        [Parameter(Mandatory = $true)]
7        [hashtable]$Rules
8    )
9    
10    $extension = $File.Extension.ToLower()
11    
12    # Check each rule category
13    foreach ($category in $Rules.Keys) {
14        if ($Rules[$category] -contains $extension) {
15            return $category
16        }
17    }
18    
19    # Default category for unmatched files
20    return "Other"
21}

This function takes a file and checks it against our rules. If no rule matches, files go to an "Other" folder so nothing gets lost.

Step 3: Handle File Naming Conflicts

What happens when you try to move report.pdf but one already exists? We need smart conflict handling:

powershell
1function Get-UniqueFileName {
2    param (
3        [Parameter(Mandatory = $true)]
4        [string]$DestinationFolder,
5        
6        [Parameter(Mandatory = $true)]
7        [string]$FileName
8    )
9    
10    $baseName = [System.IO.Path]::GetFileNameWithoutExtension($FileName)
11    $extension = [System.IO.Path]::GetExtension($FileName)
12    $newPath = Join-Path $DestinationFolder $FileName
13    
14    # If no conflict, return original name
15    if (-not (Test-Path $newPath)) {
16        return $FileName
17    }
18    
19    # Find a unique name with incrementing number
20    $counter = 1
21    do {
22        $newFileName = "{0}_{1}{2}" -f $baseName, $counter, $extension
23        $newPath = Join-Path $DestinationFolder $newFileName
24        $counter++
25    } while (Test-Path $newPath)
26    
27    return $newFileName
28}

This appends _1, _2, etc. to create unique filenames. No files get overwritten, no data gets lost.

Step 4: Build the Main Organization Function

Here's the core logic that ties everything together:

powershell
1function Invoke-FileOrganization {
2    [CmdletBinding(SupportsShouldProcess)]
3    param (
4        [Parameter(Mandatory = $true)]
5        [ValidateScript({ Test-Path $_ -PathType Container })]
6        [string]$SourcePath,
7        
8        [Parameter(Mandatory = $false)]
9        [string]$DestinationPath,
10        
11        [Parameter(Mandatory = $false)]
12        [hashtable]$Rules,
13        
14        [Parameter(Mandatory = $false)]
15        [switch]$IncludeSubfolders,
16        
17        [Parameter(Mandatory = $false)]
18        [string[]]$ExcludePatterns = @()
19    )
20    
21    # Use source as destination if not specified
22    if (-not $DestinationPath) {
23        $DestinationPath = $SourcePath
24    }
25    
26    # Use default rules if not specified
27    if (-not $Rules) {
28        $Rules = $script:OrganizationRules
29    }
30    
31    # Get all files
32    $getChildItemParams = @{
33        Path = $SourcePath
34        File = $true
35        ErrorAction = "SilentlyContinue"
36    }
37    
38    if ($IncludeSubfolders) {
39        $getChildItemParams.Recurse = $true
40    }
41    
42    $files = Get-ChildItem @getChildItemParams
43    
44    # Filter out excluded patterns
45    foreach ($pattern in $ExcludePatterns) {
46        $files = $files | Where-Object { $_.Name -notlike $pattern }
47    }
48    
49    Write-Log "Found $($files.Count) files to organize"
50    
51    $moved = 0
52    $skipped = 0
53    $errors = 0
54    
55    foreach ($file in $files) {
56        # Get the target category
57        $category = Get-FileCategory -File $file -Rules $Rules
58        $targetFolder = Join-Path $DestinationPath $category
59        
60        # Skip if file is already in the correct folder
61        if ($file.DirectoryName -eq $targetFolder) {
62            $skipped++
63            continue
64        }
65        
66        # Create target folder if needed
67        if (-not (Test-Path $targetFolder)) {
68            if ($PSCmdlet.ShouldProcess($targetFolder, "Create folder")) {
69                New-Item -ItemType Directory -Path $targetFolder -Force | Out-Null
70                Write-Log "Created folder: $category"
71            }
72        }
73        
74        # Get unique filename to avoid conflicts
75        $uniqueName = Get-UniqueFileName -DestinationFolder $targetFolder -FileName $file.Name
76        $targetPath = Join-Path $targetFolder $uniqueName
77        
78        # Move the file
79        if ($PSCmdlet.ShouldProcess($file.Name, "Move to $category")) {
80            try {
81                Move-Item -Path $file.FullName -Destination $targetPath -ErrorAction Stop
82                $moved++
83                
84                if ($uniqueName -ne $file.Name) {
85                    Write-Log "Moved: $($file.Name) -> $category\$uniqueName (renamed)"
86                }
87                else {
88                    Write-Log "Moved: $($file.Name) -> $category"
89                }
90            }
91            catch {
92                Write-Log "Error moving $($file.Name): $_" -Level "ERROR"
93                $errors++
94            }
95        }
96    }
97    
98    return @{
99        TotalFiles = $files.Count
100        Moved = $moved
101        Skipped = $skipped
102        Errors = $errors
103    }
104}

Step 5: Add Date-Based Organization

Sometimes you want to organize by date instead of type. Here's an optional date-based organizer:

powershell
1function Invoke-DateBasedOrganization {
2    [CmdletBinding(SupportsShouldProcess)]
3    param (
4        [Parameter(Mandatory = $true)]
5        [string]$SourcePath,
6        
7        [Parameter(Mandatory = $false)]
8        [ValidateSet("Year", "YearMonth", "YearMonthDay")]
9        [string]$DateFormat = "YearMonth",
10        
11        [Parameter(Mandatory = $false)]
12        [ValidateSet("Created", "Modified")]
13        [string]$DateProperty = "Modified"
14    )
15    
16    $files = Get-ChildItem -Path $SourcePath -File -ErrorAction SilentlyContinue
17    
18    Write-Log "Organizing $($files.Count) files by $DateProperty date ($DateFormat format)"
19    
20    $moved = 0
21    
22    foreach ($file in $files) {
23        # Get the relevant date
24        $date = if ($DateProperty -eq "Created") {
25            $file.CreationTime
26        }
27        else {
28            $file.LastWriteTime
29        }
30        
31        # Format the folder name based on preference
32        $folderName = switch ($DateFormat) {
33            "Year" { $date.ToString("yyyy") }
34            "YearMonth" { $date.ToString("yyyy-MM") }
35            "YearMonthDay" { $date.ToString("yyyy-MM-dd") }
36        }
37        
38        $targetFolder = Join-Path $SourcePath $folderName
39        
40        # Skip if already in correct folder
41        if ($file.DirectoryName -eq $targetFolder) {
42            continue
43        }
44        
45        if (-not (Test-Path $targetFolder)) {
46            if ($PSCmdlet.ShouldProcess($targetFolder, "Create folder")) {
47                New-Item -ItemType Directory -Path $targetFolder -Force | Out-Null
48            }
49        }
50        
51        $uniqueName = Get-UniqueFileName -DestinationFolder $targetFolder -FileName $file.Name
52        $targetPath = Join-Path $targetFolder $uniqueName
53        
54        if ($PSCmdlet.ShouldProcess($file.Name, "Move to $folderName")) {
55            try {
56                Move-Item -Path $file.FullName -Destination $targetPath -ErrorAction Stop
57                $moved++
58                Write-Log "Moved: $($file.Name) -> $folderName"
59            }
60            catch {
61                Write-Log "Error moving $($file.Name): $_" -Level "ERROR"
62            }
63        }
64    }
65    
66    Write-Log "Date organization complete: $moved files moved" -Level "SUCCESS"
67}

The Complete Script

Here's the full, production-ready script:

powershell
1<#
2.SYNOPSIS
3    Automatically organizes files into folders by type, date, or custom rules.
4
5.DESCRIPTION
6    This script scans a folder and organizes files into subfolders based on:
7    - File type/extension (Documents, Images, Videos, etc.)
8    - Date (Year, Year-Month, or Year-Month-Day folders)
9    - Custom rules you define
10    
11    Supports dry-run mode, conflict handling, and comprehensive logging.
12
13.PARAMETER SourcePath
14    The folder containing files to organize.
15
16.PARAMETER DestinationPath
17    Where to create organized folders. Defaults to SourcePath.
18
19.PARAMETER OrganizeBy
20    How to organize: "Type" (default) or "Date"
21
22.PARAMETER DateFormat
23    For date organization: "Year", "YearMonth", or "YearMonthDay"
24
25.PARAMETER IncludeSubfolders
26    Also organize files in subfolders.
27
28.PARAMETER ExcludePatterns
29    File patterns to skip (e.g., "*.tmp", "desktop.ini")
30
31.PARAMETER WhatIf
32    Preview changes without moving files.
33
34.EXAMPLE
35    .\OrganizeFiles.ps1 -SourcePath "C:\Users\You\Downloads"
36    Organizes Downloads folder by file type.
37
38.EXAMPLE
39    .\OrganizeFiles.ps1 -SourcePath "C:\Photos" -OrganizeBy Date -DateFormat YearMonth
40    Organizes photos into Year-Month folders.
41
42.EXAMPLE
43    .\OrganizeFiles.ps1 -SourcePath "C:\Messy" -WhatIf
44    Preview organization without moving files.
45
46.NOTES
47    Author: Chris Anderson
48    Date: 2025-11-10
49    Version: 1.0
50    Requires: PowerShell 5.1 or higher
51#>
52
53[CmdletBinding(SupportsShouldProcess)]
54param (
55    [Parameter(Mandatory = $true, Position = 0)]
56    [ValidateScript({ Test-Path $_ -PathType Container })]
57    [string]$SourcePath,
58    
59    [Parameter(Mandatory = $false)]
60    [string]$DestinationPath,
61    
62    [Parameter(Mandatory = $false)]
63    [ValidateSet("Type", "Date")]
64    [string]$OrganizeBy = "Type",
65    
66    [Parameter(Mandatory = $false)]
67    [ValidateSet("Year", "YearMonth", "YearMonthDay")]
68    [string]$DateFormat = "YearMonth",
69    
70    [Parameter(Mandatory = $false)]
71    [switch]$IncludeSubfolders,
72    
73    [Parameter(Mandatory = $false)]
74    [string[]]$ExcludePatterns = @("desktop.ini", "thumbs.db", "*.tmp")
75)
76
77# ============================================================================
78# Configuration
79# ============================================================================
80
81$LogPath = "$env:USERPROFILE\Documents\OrganizeLogs"
82$LogFile = Join-Path $LogPath "Organize_$(Get-Date -Format 'yyyy-MM-dd_HHmmss').log"
83
84# File organization rules
85$OrganizationRules = @{
86    "Documents"  = @(".pdf", ".doc", ".docx", ".txt", ".rtf", ".odt", ".xls", ".xlsx", ".ppt", ".pptx", ".pages", ".numbers", ".key")
87    "Images"     = @(".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp", ".ico", ".tiff", ".heic", ".raw")
88    "Videos"     = @(".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".m4v", ".mpeg")
89    "Audio"      = @(".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma", ".m4a", ".aiff")
90    "Archives"   = @(".zip", ".rar", ".7z", ".tar", ".gz", ".bz2", ".xz")
91    "Installers" = @(".exe", ".msi", ".msix", ".appx", ".dmg", ".pkg")
92    "Code"       = @(".ps1", ".py", ".js", ".ts", ".html", ".css", ".json", ".xml", ".yml", ".yaml", ".sh", ".bat", ".cmd", ".java", ".cs", ".cpp", ".c", ".h", ".go", ".rs", ".rb", ".php")
93    "Data"       = @(".csv", ".sql", ".db", ".sqlite", ".accdb", ".mdb")
94    "Ebooks"     = @(".epub", ".mobi", ".azw", ".azw3")
95    "Fonts"      = @(".ttf", ".otf", ".woff", ".woff2", ".eot")
96}
97
98# Ensure log directory exists
99if (-not (Test-Path $LogPath)) {
100    New-Item -ItemType Directory -Path $LogPath -Force | Out-Null
101}
102
103# ============================================================================
104# Functions
105# ============================================================================
106
107function Write-Log {
108    param (
109        [string]$Message,
110        [ValidateSet("INFO", "WARN", "ERROR", "SUCCESS")]
111        [string]$Level = "INFO"
112    )
113    
114    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
115    $logMessage = "[$timestamp] [$Level] $Message"
116    
117    Add-Content -Path $LogFile -Value $logMessage
118    
119    switch ($Level) {
120        "INFO"    { Write-Host $logMessage -ForegroundColor Cyan }
121        "WARN"    { Write-Host $logMessage -ForegroundColor Yellow }
122        "ERROR"   { Write-Host $logMessage -ForegroundColor Red }
123        "SUCCESS" { Write-Host $logMessage -ForegroundColor Green }
124    }
125}
126
127function Get-FileCategory {
128    param (
129        [System.IO.FileInfo]$File,
130        [hashtable]$Rules
131    )
132    
133    $extension = $File.Extension.ToLower()
134    
135    foreach ($category in $Rules.Keys) {
136        if ($Rules[$category] -contains $extension) {
137            return $category
138        }
139    }
140    
141    return "Other"
142}
143
144function Get-UniqueFileName {
145    param (
146        [string]$DestinationFolder,
147        [string]$FileName
148    )
149    
150    $baseName = [System.IO.Path]::GetFileNameWithoutExtension($FileName)
151    $extension = [System.IO.Path]::GetExtension($FileName)
152    $newPath = Join-Path $DestinationFolder $FileName
153    
154    if (-not (Test-Path $newPath)) {
155        return $FileName
156    }
157    
158    $counter = 1
159    do {
160        $newFileName = "{0}_{1}{2}" -f $baseName, $counter, $extension
161        $newPath = Join-Path $DestinationFolder $newFileName
162        $counter++
163    } while (Test-Path $newPath)
164    
165    return $newFileName
166}
167
168function Invoke-TypeOrganization {
169    [CmdletBinding(SupportsShouldProcess)]
170    param (
171        [string]$SourcePath,
172        [string]$DestinationPath,
173        [switch]$IncludeSubfolders,
174        [string[]]$ExcludePatterns
175    )
176    
177    $getChildItemParams = @{
178        Path = $SourcePath
179        File = $true
180        ErrorAction = "SilentlyContinue"
181    }
182    
183    if ($IncludeSubfolders) {
184        $getChildItemParams.Recurse = $true
185    }
186    
187    $files = Get-ChildItem @getChildItemParams
188    
189    # Apply exclusion patterns
190    foreach ($pattern in $ExcludePatterns) {
191        $files = $files | Where-Object { $_.Name -notlike $pattern }
192    }
193    
194    Write-Log "Found $($files.Count) files to organize by type"
195    
196    $moved = 0
197    $skipped = 0
198    $errors = 0
199    
200    foreach ($file in $files) {
201        $category = Get-FileCategory -File $file -Rules $OrganizationRules
202        $targetFolder = Join-Path $DestinationPath $category
203        
204        if ($file.DirectoryName -eq $targetFolder) {
205            $skipped++
206            continue
207        }
208        
209        if (-not (Test-Path $targetFolder)) {
210            if ($PSCmdlet.ShouldProcess($targetFolder, "Create folder")) {
211                New-Item -ItemType Directory -Path $targetFolder -Force | Out-Null
212                Write-Log "Created folder: $category"
213            }
214        }
215        
216        $uniqueName = Get-UniqueFileName -DestinationFolder $targetFolder -FileName $file.Name
217        $targetPath = Join-Path $targetFolder $uniqueName
218        
219        if ($PSCmdlet.ShouldProcess($file.Name, "Move to $category")) {
220            try {
221                Move-Item -Path $file.FullName -Destination $targetPath -ErrorAction Stop
222                $moved++
223                Write-Log "Moved: $($file.Name) -> $category"
224            }
225            catch {
226                Write-Log "Error moving $($file.Name): $_" -Level "ERROR"
227                $errors++
228            }
229        }
230    }
231    
232    return @{
233        TotalFiles = $files.Count
234        Moved = $moved
235        Skipped = $skipped
236        Errors = $errors
237    }
238}
239
240function Invoke-DateOrganization {
241    [CmdletBinding(SupportsShouldProcess)]
242    param (
243        [string]$SourcePath,
244        [string]$DestinationPath,
245        [string]$DateFormat,
246        [switch]$IncludeSubfolders,
247        [string[]]$ExcludePatterns
248    )
249    
250    $getChildItemParams = @{
251        Path = $SourcePath
252        File = $true
253        ErrorAction = "SilentlyContinue"
254    }
255    
256    if ($IncludeSubfolders) {
257        $getChildItemParams.Recurse = $true
258    }
259    
260    $files = Get-ChildItem @getChildItemParams
261    
262    foreach ($pattern in $ExcludePatterns) {
263        $files = $files | Where-Object { $_.Name -notlike $pattern }
264    }
265    
266    Write-Log "Found $($files.Count) files to organize by date ($DateFormat)"
267    
268    $moved = 0
269    $skipped = 0
270    $errors = 0
271    
272    foreach ($file in $files) {
273        $date = $file.LastWriteTime
274        
275        $folderName = switch ($DateFormat) {
276            "Year" { $date.ToString("yyyy") }
277            "YearMonth" { $date.ToString("yyyy-MM") }
278            "YearMonthDay" { $date.ToString("yyyy-MM-dd") }
279        }
280        
281        $targetFolder = Join-Path $DestinationPath $folderName
282        
283        if ($file.DirectoryName -eq $targetFolder) {
284            $skipped++
285            continue
286        }
287        
288        if (-not (Test-Path $targetFolder)) {
289            if ($PSCmdlet.ShouldProcess($targetFolder, "Create folder")) {
290                New-Item -ItemType Directory -Path $targetFolder -Force | Out-Null
291                Write-Log "Created folder: $folderName"
292            }
293        }
294        
295        $uniqueName = Get-UniqueFileName -DestinationFolder $targetFolder -FileName $file.Name
296        $targetPath = Join-Path $targetFolder $uniqueName
297        
298        if ($PSCmdlet.ShouldProcess($file.Name, "Move to $folderName")) {
299            try {
300                Move-Item -Path $file.FullName -Destination $targetPath -ErrorAction Stop
301                $moved++
302                Write-Log "Moved: $($file.Name) -> $folderName"
303            }
304            catch {
305                Write-Log "Error moving $($file.Name): $_" -Level "ERROR"
306                $errors++
307            }
308        }
309    }
310    
311    return @{
312        TotalFiles = $files.Count
313        Moved = $moved
314        Skipped = $skipped
315        Errors = $errors
316    }
317}
318
319# ============================================================================
320# Main Execution
321# ============================================================================
322
323Write-Log "========================================" -Level "INFO"
324Write-Log "File Organization Script Started" -Level "INFO"
325Write-Log "Source: $SourcePath" -Level "INFO"
326Write-Log "Organize By: $OrganizeBy" -Level "INFO"
327Write-Log "========================================" -Level "INFO"
328
329# Set destination to source if not specified
330if (-not $DestinationPath) {
331    $DestinationPath = $SourcePath
332}
333
334# Run the appropriate organization method
335$result = if ($OrganizeBy -eq "Type") {
336    Invoke-TypeOrganization -SourcePath $SourcePath -DestinationPath $DestinationPath `
337        -IncludeSubfolders:$IncludeSubfolders -ExcludePatterns $ExcludePatterns
338}
339else {
340    Invoke-DateOrganization -SourcePath $SourcePath -DestinationPath $DestinationPath `
341        -DateFormat $DateFormat -IncludeSubfolders:$IncludeSubfolders -ExcludePatterns $ExcludePatterns
342}
343
344# Summary
345Write-Log "========================================" -Level "INFO"
346Write-Log "Organization Complete!" -Level "SUCCESS"
347Write-Log "Total files processed: $($result.TotalFiles)" -Level "INFO"
348Write-Log "Files moved: $($result.Moved)" -Level "SUCCESS"
349Write-Log "Files skipped (already organized): $($result.Skipped)" -Level "INFO"
350if ($result.Errors -gt 0) {
351    Write-Log "Errors encountered: $($result.Errors)" -Level "WARN"
352}
353Write-Log "Log saved to: $LogFile" -Level "INFO"
354Write-Log "========================================" -Level "INFO"

How to Run This Script

Method 1: Interactive Execution

powershell
1# Organize Downloads by file type
2.\OrganizeFiles.ps1 -SourcePath "$env:USERPROFILE\Downloads"
3
4# Preview first (highly recommended!)
5.\OrganizeFiles.ps1 -SourcePath "$env:USERPROFILE\Downloads" -WhatIf
6
7# Organize photos by date
8.\OrganizeFiles.ps1 -SourcePath "D:\Photos" -OrganizeBy Date -DateFormat YearMonth
9
10# Include subfolders
11.\OrganizeFiles.ps1 -SourcePath "C:\Projects" -IncludeSubfolders

Method 2: Scheduled Task

Set up a weekly task to keep your Downloads organized:

  1. Open Task Scheduler
  2. Create Task: "Weekly Downloads Organization"
  3. Trigger: Weekly on Sunday at 10 PM
  4. Action: powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\OrganizeFiles.ps1" -SourcePath "C:\Users\You\Downloads"

Customization Options

ParameterDefaultDescription
SourcePath(Required)Folder to organize
DestinationPathSame as SourceWhere to create organized folders
OrganizeByType"Type" or "Date"
DateFormatYearMonth"Year", "YearMonth", or "YearMonthDay"
IncludeSubfolders$falseAlso process files in subfolders
ExcludePatternsSystem filesFile patterns to skip

Adding Custom Categories

Edit the $OrganizationRules hashtable in the script:

powershell
1# Add a new category
2$OrganizationRules["3DModels"] = @(".stl", ".obj", ".fbx", ".blend")
3
4# Add extensions to existing category
5$OrganizationRules["Images"] += @(".psd", ".ai", ".sketch")

Security Considerations

⚠️ Important notes:

  • Always run with -WhatIf first to preview changes
  • The script moves files, not copies—originals are relocated
  • Back up important files before running on new folders
  • Excluded patterns prevent moving system files
  • Log files provide an audit trail of all operations

Common Issues & Solutions

IssueCauseSolution
"Access Denied"File is open/lockedClose applications using the file
Files not movingAlready in correct folderIntentional—skipped to avoid loops
Wrong categoryExtension not in rulesAdd extension to appropriate category
Script won't runExecution policySet-ExecutionPolicy RemoteSigned -Scope CurrentUser
Duplicate namesConflict handlingAutomatic rename with _1, _2 suffix

Taking It Further

Enhance the script with these advanced features:

  • Smart screenshots: Detect screenshot naming patterns and create a Screenshots folder
  • Project grouping: Group related files by name prefix
  • Size-based organization: Separate large files (videos over 1GB)
  • Duplicate detection: Find and handle duplicate files
  • Undo capability: Log moves to enable reversal
  • Email summary: Send organization reports

Conclusion

You've built a powerful file organization automation that adapts to your needs. Whether you prefer sorting by type (most common) or by date (great for photos), this script handles it all.

The key insight is that file organization shouldn't require your attention. Set up a scheduled task, customize the rules to match your workflow, and let the script maintain order automatically. Your future self—the one who needs to find that important document from last month—will thank you.

Start with your Downloads folder. Run the preview mode first. Watch the magic happen. Then expand to other folders that need taming.

Chaos has met its match. Happy organizing!

Sponsored Content

Interested in advertising? Reach automation professionals through our platform.

Share this article