<# .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 }