# PowerShell Folder Organizer
A robust PowerShell automation script that reorganizes chaotic folder structures into a clean, categorized hierarchy. Originally designed for "Note Nest" personal knowledge management system, it can be adapted for any folder reorganization task.
Purpose and Problem
The Problem
Personal knowledge management systems often become messy over time:
- Files scattered across multiple folders
- Inconsistent naming conventions
- No clear organization structure
- Difficult to find anything
The Solution
This script automatically:
- Creates a new organized folder structure
- Categorizes files based on naming patterns
- Handles special cases (date-based folders, project files)
- Logs all operations for review
- Cleans up empty source directories
Folder Structure Created
New Structure Definition
| Folder | Purpose |
|---|---|
00_Inbox | Uncategorized/new files awaiting sorting |
01_Projects | Client projects, contracted work |
02_Areas | Ongoing areas of responsibility |
03_Resources | Reference materials, templates |
04_Archives | Completed/deprecated items |
05_Journal | Daily logs, time-based entries |
06_Assets | Media files, images, diagrams |
07_Temp | Temporary working files |
Core Features
1. Safe Move Operations
function Move-ItemSafely {
param (
[string]$RelativeSourcePath,
[string]$DestinationFolder,
[string]$NewName = ''
)
$SourceFullPath = Join-Path $RootPath $RelativeSourcePath
$DestFullPath = Join-Path $RootPath $DestinationFolder
# Create destination if it doesn't exist
if (-not (Test-Path $DestFullPath)) {
New-Item -Path $DestFullPath -ItemType Directory -Force | Out-Null
}
# Determine final destination
$FinalDest = if ($NewName) {
Join-Path $DestFullPath $NewName
} else {
Join-Path $DestFullPath (Split-Path $SourceFullPath -Leaf)
}
try {
Move-Item -LiteralPath $SourceFullPath -Destination $FinalDest -Force -ErrorAction Stop
Add-Content -Path $LogFile -Value "MOVED: $RelativeSourcePath `n TO: $FinalDest"
} catch {
Add-Content -Path $LogFile -Value "ERROR moving $RelativeSourcePath : $($_.Exception.Message)"
}
}Features:
- Creates destination folders automatically
- Handles name conflicts with -Force
- Logs both success and errors
- Uses -LiteralPath for special characters in paths
2. Comprehensive Logging
$LogFile = Join-Path $RootPath "reorganization_log_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"Every operation is logged with:
- Timestamp
- Source path
- Destination path
- Errors (if any)
Log Example:
--- Reorganization Log: 2025-06-30 10:15:32 ---
MOVED: Note Nest\Collections\About Us.html
TO: E:\localhost\...\01_Projects\Namani_Construction\Website_Development\About Us.html
MOVED: Note Nest\June\30-06-2024\Daily Log.md
TO: E:\...\05_Journal\2024\06_June\30-06-2024_Daily_Log_Daily Log.md
--- Reorganization Complete: 2025-06-30 10:16:45 ---3. Pattern-Based File Routing
The script uses wildcard pattern matching to categorize files:
switch -Wildcard ($_.Name) {
'About Us.html' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "01_Projects\Namani_Construction\Website_Development" }
'blog.html' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "01_Projects\Namani_Construction\Website_Development" }
'*ASK BASKET*' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "04_Archives\Historical_Client_Docs" }
'AI Learning Roadmap*' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "02_Areas\AI_Automation" }
'Pasted image*.png' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "06_Assets\Images" }
'Untitled*' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "00_Inbox" }
default { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "00_Inbox" }
}4. Special Folder Handling
#### June Date-Based Folder Processing
$juneRoot = Join-Path $RootPath "Note Nest\June"
if (Test-Path $juneRoot) {
Get-ChildItem -Path $juneRoot -Directory | ForEach-Object {
$dateFolder = $_
if ($dateFolder.Name -match '(\d{2})-(\d{2})-(\d{4})') {
$year = $Matches[3]
$month = $Matches[2]
$day = $Matches[1]
$monthName = (Get-Culture).DateTimeFormat.GetMonthName([int]$month)
$destJournalFolder = "05_Journal\$year\$($month)_$monthName"
Get-ChildItem -Path $dateFolder.FullName -File | ForEach-Object {
$file = $_
$fileRelativePath = $file.FullName.Substring($RootPath.Length + 1)
$newLogFileName = "$($year)-$($month)-$($day)_Daily_Log_$($file.Name)"
Move-ItemSafely -RelativeSourcePath $fileRelativePath -DestinationFolder $destJournalFolder -NewName $newLogFileName
}
}
}
}Converts: Note Nest\June\30-06-2024\Daily Log.md To: 05_Journal\2024\06_June\30-06-2024_Daily_Log_Daily Log.md
5. Folder Filtering
Files in certain directories are skipped to prevent duplicates:
if ($relativePath -like '*\Site Structure\*' -or $relativePath -like '*\June\*') { return }6. Source Directory Cleanup
After reorganization, empty source directories are removed:
@( "Collections", "Note Nest", "Prompt Wrapper", "Scripts" ) | ForEach-Object {
$dirPath = Join-Path $RootPath $_
if (Test-Path $dirPath) {
try { Remove-Item -Path $dirPath -Recurse -Force -ErrorAction Stop } catch {}
}
}Usage
Prerequisites
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -ForceRunning the Script
.\reorganize_PERFECT.ps1 -RootPath "E:\localhost\Note Nest_COPY"Or use the launcher:
cd C:\Users\bhargavxharma\OneDrive\Desktop
.\reorganize_PERFECT.ps1 -RootPath "E:\localhost\Note Nest_COPY"Parameters
| Parameter | Required | Description |
|---|---|---|
| RootPath | Yes | Path to the root folder to reorganize |
File Categorization Examples
| File Pattern | Destination |
|---|---|
| HTML files for clients | 01_Projects\{Client}\Website_Development |
| AI/Learning notes | 02_Areas\AI_Automation |
| Frontend code snippets | 02_Areas\Code_Development\Frontend_UI_Snippets |
| Server admin docs | 02_Areas\Linux_Server_Administration |
| Prompt templates | 03_Resources\Prompt_Templates |
| Personal reference | 03_Resources\Personal_Reference |
| Deprecated code | 04_Archives\Deprecated_Code_Dev_Playground |
| Image files | 06_Assets\Images |
| SVG diagrams | 06_Assets\Diagrams_SVGs |
| Untitled/Unknown | 00_Inbox |
Safety Features
- Relative Paths: All operations use relative paths from RootPath
- Existence Checks: Verify files/folders exist before moving
- Error Handling: Try-catch blocks around all Move-Item operations
- Logging: Complete audit trail of all operations
- Destination Creation: Auto-create missing destination folders
- No Delete: Only moves files, never deletes content
Customization Guide
Adding New File Patterns
Add new cases to the switch statement:
'YourPattern*' {
Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "Target_Folder"
}Changing Folder Structure
Modify the folder creation section:
# Create new structure
@( "00_Inbox", "01_Projects", "02_Areas", "03_Resources", "04_Archives", "05_Journal", "06_Assets", "07_Temp" ) | ForEach-Object {
New-Item -Path (Join-Path $RootPath $_) -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
}Adding New Source Folders
Process additional source directories:
Get-ChildItem -Path (Join-Path $RootPath "YourSourceFolder") -Recurse -File | ForEach-Object {
$relativePath = $_.FullName.Substring($RootPath.Length + 1)
# Add your routing logic here
}Version History
| Version | Date | Changes |
|---|---|---|
| PERFECT | June 2025 | Final version with June folder bugfix |
| v2 | Earlier | Intermediate version |
Use Cases
- Personal Knowledge Management: Reorganize Obsidian/Notion/OneNote exports
- Project Cleanup: Consolidate scattered project files
- Migration: Move files from old structure to new
- Archive Organization: Sort through historical files
- Client Files: Categorize client deliverables
Limitations
- Windows Only: Uses PowerShell and Windows-specific cmdlets
- Specific Structure: Designed for "Note Nest" pattern, needs adaptation for other structures
- No Undo: Moves files permanently (can restore from log if needed)
- Pattern Matching: Some files may need manual sorting to inbox
Security Considerations
- Execution Policy: Requires RemoteSigned policy (safe default)
- No Network Operations: Runs entirely local
- Log Review: Always check log after running
- Test First: Run on copy of data before original
Example Workflow
- Backup: Copy the folder to reorganize
- Test: Run script on copy
- Review: Check log file and 00_Inbox
- Adjust: Modify patterns if needed
- Apply: Run on original data
Detailed Implementation Analysis
Core Function: Move-ItemSafely
This is the workhorse function that handles all file moves with safety checks:
function Move-ItemSafely {
param (
[string]$RelativeSourcePath,
[string]$DestinationFolder,
[string]$NewName = ''
)
# Construct full paths
$SourceFullPath = Join-Path $RootPath $RelativeSourcePath
$DestFullPath = Join-Path $RootPath $DestinationFolder
# Step 1: Check if source exists
if (-not (Test-Path $SourceFullPath)) {
return # Silent return for non-existent files
}
# Step 2: Create destination if needed
if (-not (Test-Path $DestFullPath)) {
New-Item -Path $DestFullPath -ItemType Directory -Force | Out-Null
}
# Step 3: Determine final filename
$FinalDest = if ($NewName) {
Join-Path $DestFullPath $NewName
} else {
Join-Path $DestFullPath (Split-Path $SourceFullPath -Leaf)
}
# Step 4: Attempt move with error handling
try {
Move-Item -LiteralPath $SourceFullPath -Destination $FinalDest -Force -ErrorAction Stop
Add-Content -Path $LogFile -Value "MOVED: $RelativeSourcePath `n TO: $FinalDest"
} catch {
Add-Content -Path $LogFile -Value "ERROR moving $RelativeSourcePath : $($_.Exception.Message)"
}
}Why this approach?
- Relative paths: Prevents typos from moving wrong files
- Silent return on missing source: Handles gracefully when file already moved
- Auto-create destinations: No need to pre-create folder structure
- -Force flag: Overwrites existing files if name collision
- -LiteralPath: Handles special characters in paths (spaces, brackets)
- Try-catch: Captures and logs errors without stopping script
Processing Pipeline
The script processes files in a specific order to handle dependencies:
Phase 1: Create Folder Structure
# Create new structure
@( "00_Inbox", "01_Projects", "02_Areas", "03_Resources", "04_Archives", "05_Journal", "06_Assets", "07_Temp" ) | ForEach-Object {
New-Item -Path (Join-Path $RootPath $_) -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
}Phase 2: Pre-Process Special Folders
Handle folders that need special attention before general processing:
# Special folder: Site Structure
Move-ItemSafely -RelativeSourcePath "Note Nest\Important\Namani Construction\Site Structure" -DestinationFolder "01_Projects\Namani_Construction\Project_Documentation"Phase 3: Process Collections
The Collections folder often contains mixed files:
Get-ChildItem -Path (Join-Path $RootPath "Collections") -Recurse -File | ForEach-Object {
$relativePath = $_.FullName.Substring($RootPath.Length + 1)
switch -Wildcard ($_.Name) {
# HTML files → Website Development
'About Us.html' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "01_Projects\Namani_Construction\Website_Development" }
'blog.html' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "01_Projects\Namani_Construction\Website_Development" }
# Brand files → Client Project
'ASKBASKET Brand Guidelines.docx' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "01_Projects\AskBasket" }
# PDFs → Resources
'Flexible Website*.pdf' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "03_Resources\Pricing_Frameworks" }
# Unknown → Inbox
default { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "00_Inbox" }
}
}Phase 4: Process Note Nest Inner
The main note folder with many subfolders:
Get-ChildItem -Path (Join-Path $RootPath "Note Nest") -Recurse -File | ForEach-Object {
# Skip already-processed special folders
if ($relativePath -like '*\Site Structure\*' -or $relativePath -like '*\June\*') { return }
# Pattern matching for routing
switch -Wildcard ($_.Name) {
'AI Learning Roadmap*' { Move-ItemSafely ... -DestinationFolder "02_Areas\AI_Automation" }
'Pasted image*.png' { Move-ItemSafely ... -DestinationFolder "06_Assets\Images" }
}
}Phase 5: Cleanup
Remove empty source directories:
@( "Collections", "Note Nest", "Prompt Wrapper", "Scripts" ) | ForEach-Object {
$dirPath = Join-Path $RootPath $_
if (Test-Path $dirPath) {
try { Remove-Item -Path $dirPath -Recurse -Force -ErrorAction Stop } catch {}
}
}Date-Based Folder Processing
One of the most complex parts is handling date-based folders:
$juneRoot = Join-Path $RootPath "Note Nest\June"
if (Test-Path $juneRoot) {
# Get all subdirectories (each is a date folder like "30-06-2024")
Get-ChildItem -Path $juneRoot -Directory | ForEach-Object {
$dateFolder = $_
# Extract date components from folder name
if ($dateFolder.Name -match '(\d{2})-(\d{2})-(\d{4})') {
$year = $Matches[3] # 2024
$month = $Matches[2] # 06
$day = $Matches[1] # 30
# Convert month number to name
$monthName = (Get-Culture).DateTimeFormat.GetMonthName([int]$month)
# Determine destination: 05_Journal\2024\06_June
$destJournalFolder = "05_Journal\$year\$($month)_$monthName"
# Get all files in this date folder
Get-ChildItem -Path $dateFolder.FullName -File | ForEach-Object {
$file = $_
$fileRelativePath = $file.FullName.Substring($RootPath.Length + 1)
# Create new standardized name: "2024-06-30_Daily_Log_OriginalName"
$newLogFileName = "$($year)-$($month)-$($day)_Daily_Log_$($file.Name)"
Move-ItemSafely -RelativeSourcePath $fileRelativePath `
-DestinationFolder $destJournalFolder `
-NewName $newLogFileName
}
}
}
}Input → Output transformation:
Note Nest\June\30-06-2024\Daily Log.md
→ 05_Journal\2024\06_June\30-06-2024_Daily_Log_Daily Log.mdConditional Routing
Some files need conditional logic based on their location:
'Website.md' {
# Only move if it's in an Aatmanova folder
if ($_.DirectoryName -like '*Aatmanova*') {
Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "01_Projects\Aatmanova_Website"
}
}
'fortress*.sh' {
# Only move if parent folder is "Secure VPS"
if ($_.Directory.Name -eq 'Secure VPS') {
Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "01_Projects\VPS_Fortress_Development"
}
}Error Handling Strategy
The script uses multiple layers of error handling:
- Function-level: Move-ItemSafely has try-catch
- Folder-level: Silently continue if folder doesn't exist
- Removal-level: Silently continue if can't remove folder
- Logging: All errors written to log file
# Safe folder creation (non-terminating errors)
New-Item -Path (Join-Path $RootPath $_) -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
# Safe folder removal
try { Remove-Item -Path $dirPath -Recurse -Force -ErrorAction Stop } catch {}Performance Considerations
For large folder structures, consider these optimizations:
# Use -Recurse carefully
Get-ChildItem -Path (Join-Path $RootPath "Collections") -Recurse -File
# Path construction is O(1) but lots of them add up
# Using Substring is faster than repeated Join-Path
# Parallel processing (PowerShell 7+)
Get-ChildItem ... -File | ForEach-Object -Parallel {
Move-ItemSafely ...
} -ThrottleLimit 10Common Issues and Solutions
| Issue | Cause | Solution |
|---|---|---|
| Files not moving | Source path typo | Check log for errors |
| Destination not created | Permissions | Run as Administrator |
| Special characters fail | -Path instead of -LiteralPath | Script uses -LiteralPath |
| Script hangs | Infinite loop in date parsing | Check folder name regex |
| Partial completion | Error in middle | Check log, re-run |
Adapting for Different Structures
Example: Adding a New Category
To add a "Music" category:
# 1. Add folder to structure
@( "00_Inbox", "01_Projects", "02_Areas", "03_Resources", "04_Archives", "05_Journal", "06_Assets", "07_Temp", "08_Music" )
# 2. Add routing rules
'*.mp3' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "08_Music" }
'*.wav' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "08_Music" }
'Music*.md' { Move-ItemSafely -RelativeSourcePath $relativePath -DestinationFolder "08_Music" }Example: Different Date Format
If your dates are in different format (e.g., "2024-06-30"):
# Update regex pattern
if ($dateFolder.Name -match '(\d{4})-(\d{2})-(\d{2})') {
$year = $Matches[1]
$month = $Matches[2]
$day = $Matches[3]
# ... rest of logic
}Comparison with Alternatives
| Method | Pros | Cons |
|---|---|---|
| This Script | Free, customizable, local | Windows only, manual setup |
| Bulk Rename Utility | GUI, no coding | Limited pattern matching |
| PowerAutomate | No code, cloud sync | Requires Microsoft account |
| Python script | Cross-platform | Requires Python installation |
| FileExplorer | Native, no setup | Manual, no automation |
Conclusion
This PowerShell folder organizer demonstrates:
- Robust error handling with logging
- Pattern-based routing for automatic categorization
- Safe move operations that preserve data
- Cleanup automation for removing empty folders
- Customizability for different folder structures
It can be adapted for any reorganization task by modifying the switch patterns and folder structure definitions.
Architecture Feedback
Spotted a potential optimization or antipattern? Let me know.