SCRIPT LIBRARY · POWERSHELL
Give Every Game Its Own Folder, With Updates and DLC Alongside
Moves loose game files into one folder per title, worked out from the file name, so a game's base file, updates and DLC end up together. Built for Switch backups, and it works for anything with a consistent naming pattern.
- What it does
- Works out a title from each file name (everything before the first " [" by default) and moves the file into a folder with that name, creating it if needed. Never deletes or overwrites anything.
- Requires
- Windows PowerShell 5.1 or PowerShell 7+
- No modules
- Permissions
- Read and write access to the folder. No admin rights.
- Runs on
- Windows 10/11, macOS or Linux with PowerShell 7
- Tested
- Parse-checked and run in PowerShell 7.4 against a folder of empty dummy files, with -WhatIf, for real, several files per title, a file with no delimiter and a name with a character Windows doesn't allow
Part 7 of the thread Keeping a media library tidy
Backups of your own Switch games come as a pile of files: the base game, then an update or two, then any DLC, each with the title followed by bracketed IDs and version numbers. Dump a few dozen games and the folder turns into a wall of near-identical names.
This gives every game its own folder. It reads the title from the front of each file name, makes a folder for it, and moves the file in, so a game's base file, updates and DLC sit together.
My original version also deleted stray text files and empty folders afterwards. This one doesn't delete anything; it just moves. Tidy up by hand once you've seen the result.
<#
.SYNOPSIS
Moves each game file into a folder named after its title, so a game's base file, updates and DLC sit together.
.DESCRIPTION
Works out a folder name from each file name by taking everything before the first " [" (or the delimiter you
choose), so "Space Blaster [0100ABCD00010000][v0].nsz" and "Space Blaster [0100ABCD00010800][v65536].nsz" both
go into a "Space Blaster" folder. Files with no delimiter in the name are left where they are.
Characters that aren't allowed in folder names are replaced with a dash, and trailing dots and spaces are trimmed.
Only files sitting directly in the folder are moved. Nothing is ever deleted or overwritten.
Every file gets an object back. Supports -WhatIf.
.PARAMETER Path
The folder to organize.
.PARAMETER Extension
File extensions to move. Default: .nsp, .nsz, .xci and .xcz.
.PARAMETER Delimiter
Where the title ends. Default: " [". Use " (" for No-Intro-style names like "Title (USA).zip".
.PARAMETER AllExtensions
Move every file regardless of extension.
.EXAMPLE
.\Move-GameToTitleFolder.ps1 -Path E:\Backups\switch -WhatIf
.EXAMPLE
.\Move-GameToTitleFolder.ps1 -Path D:\Games\psx -Extension .chd,.m3u -Delimiter ' ('
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]$Path,
[string[]]$Extension = @('.nsp', '.nsz', '.xci', '.xcz'),
[ValidateNotNullOrEmpty()]
[string]$Delimiter = ' [',
[switch]$AllExtensions
)
$extFilter = @($Extension | Where-Object { $_ } | ForEach-Object { if ($_ -like '.*') { $_.ToLower() } else { ".$_".ToLower() } })
$badChars = [regex]::Escape(-join [System.IO.Path]::GetInvalidFileNameChars())
$files = Get-ChildItem -LiteralPath $Path -File | Where-Object { $AllExtensions -or $extFilter -contains $_.Extension.ToLower() }
foreach ($file in $files) {
$result = [ordered]@{ Name = $file.Name; Folder = $null; Status = 'NoDelimiter' }
$cut = $file.Name.IndexOf($Delimiter)
if ($cut -le 0) { [pscustomobject]$result; continue }
$folderName = ($file.Name.Substring(0, $cut) -replace "[$badChars]", '-').Trim().TrimEnd('.', ' ')
if (-not $folderName) { [pscustomobject]$result; continue }
$result.Folder = $folderName
$destFolder = Join-Path $Path $folderName
$dest = Join-Path $destFolder $file.Name
if (Test-Path -LiteralPath $dest) {
$result.Status = 'SkippedExists'
Write-Warning "$($file.Name) is already in '$folderName'. Left alone."
[pscustomobject]$result; continue
}
if ((Test-Path -LiteralPath $destFolder) -and -not (Test-Path -LiteralPath $destFolder -PathType Container)) {
$result.Status = 'SkippedNotAFolder'
Write-Warning "'$folderName' exists as a file, not a folder. $($file.Name) left alone."
[pscustomobject]$result; continue
}
$result.Status = 'WhatIf'
if ($PSCmdlet.ShouldProcess($file.FullName, "Move into folder '$folderName'")) {
try {
if (-not (Test-Path -LiteralPath $destFolder)) { New-Item -ItemType Directory -Path $destFolder -ErrorAction Stop | Out-Null }
Move-Item -LiteralPath $file.FullName -Destination $dest -ErrorAction Stop
$result.Status = 'Moved'
}
catch {
$result.Status = "Failed: $($_.Exception.Message)"
Write-Warning "$($file.Name): $($_.Exception.Message)"
}
}
[pscustomobject]$result
}
Parameters
| Parameter | Type | Default | What it's for |
|---|---|---|---|
-Path | string | — | Required. The folder to organize. Only files directly in it are moved. |
-Extension | string[] | .nsp, .nsz, .xci, .xcz | File extensions to move. |
-Delimiter | string | " [" | Where the title ends. Use ' (' for names like "Title (USA).zip". |
-AllExtensions | switch | — | Move every file, whatever its extension. |
Run it
Preview the folders it would make.
.\Move-GameToTitleFolder.ps1 -Path E:\Backups\switch -WhatIfGroup multi-disc PlayStation games and their playlists by title.
.\Move-GameToTitleFolder.ps1 -Path D:\Games\psx -Extension .chd,.m3u -Delimiter ' ('Organize, then keep a record of every move.
.\Move-GameToTitleFolder.ps1 -Path E:\Backups\switch | Export-Csv moves.csv -NoTypeInformationWhat you'll see
Name Folder Status
---- ------ ------
Loose Game.xci NoDelimiter
Puzzle Tower [0100EEEE00020000][v0].nsp Puzzle Tower Moved
Space Blaster [0100ABCD00010000][v0].nsz Space Blaster Moved
Space Blaster [0100ABCD00010800][v65536].nsz Space Blaster Moved
How it works
- Find the files. Only files directly in the folder, and only the extensions you asked for (Switch formats by default).
- Read the title. Everything before the first
" ["(or your-Delimiter) is the title. A file without the delimiter is reported and left alone. - Make it a safe folder name. Characters Windows doesn't allow in names become a dash, and trailing dots and spaces are trimmed.
- Move it. The folder is created if needed and the file moves in. If a file with that name is already in the folder, or a file (not a folder) already has that name, it's skipped with a warning.
Take it further
- Plan the tree around it. These title folders sit inside the per-system folders from my platform folder layout.
- Undo is easy. Export the output to CSV; moving each file from its folder back to the parent puts everything where it was.
- Other systems. Anything with a consistent "Title plus tags" naming pattern works, from multi-disc PS1 games to Wii U folders. For loose files that don't follow a pattern, Move-RomByKeyword can at least pull the junk out first.
Things that'll trip you up
- The title has to be spelled the same every time. If one file says "Space Blaster" and its update says "Space Blaster!" they'll land in two folders. Fix the odd one's name and run it again; files already in a folder aren't touched.
- Some emulators want the files flat. Check that your emulator or frontend scans subfolders before you reorganize a folder it reads from.
- Nothing gets cleaned up. Text files, extra scripts and empty folders are left where they were. That's deliberate. Delete what you don't want once you've looked.