Skip to content

Instantly share code, notes, and snippets.

@haydenmc
Created February 18, 2025 09:19
Show Gist options
  • Save haydenmc/6ad47340fe706f132a754e299b76d718 to your computer and use it in GitHub Desktop.
Save haydenmc/6ad47340fe706f132a754e299b76d718 to your computer and use it in GitHub Desktop.
Rename FATX Files
$MAX_LENGTH = 42
$files = (Get-ChildItem -File -Recurse | Where-Object { $_.Name.Length -gt $MAX_LENGTH })
$validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&''()-.@[]^_`{}~ '
function Strip-Unnecessary
{
param(
[string]$In
)
return ($In -replace '\[!\]','').Trim()
}
function Abbreviate-String
{
param(
[Parameter(Position = 0)]
[string]
$In
)
$tokens = [regex]::Matches($In, '[^()\s]+|[()]|\s')
$output = ""
foreach($token in $tokens)
{
if ([Char]::IsLetter($token.Value[0]))
{
$output += [Char]::ToUpper($token.Value[0])
}
else
{
$output += $token.Value
}
}
return $output -replace '\(V\)','' # Remove useless (V)
}
# function Abbreviate-String
# {
# # Shortens each token between spaces to its first letter
# param(
# [string]$In
# )
# $DELIMITER = ' '
# $tokens = $In.Split($DELIMITER)
# $outputTokens = @()
# foreach ($token in $tokens)
# {
# if ([Char]::IsLetter($token[0]))
# {
# $outputTokens += [Char]::ToUpper($token[0])
# }
# else
# {
# $outputTokens += $token
# }
# }
# return [System.String]::Join($DELIMITER, $outputTokens)
# }
function Separate-String
{
# Attempts to split the name in a reasonable place to allow the first part to remain readable
param(
[string]$Name,
[int]$MaxLength
)
# Try to find a convenient place to split
$DEFAULT_SEPARATOR = " - "
# How much room to try to leave at the end for abbreviations
$shortenEndBuffer = [System.Math]::Min(21, $MaxLength)
$abbreviateAfter = ($MaxLength - $shortenEndBuffer)
# Try to abbreviate everything after a separating hyphen
$splitIndex = $Name.IndexOf($DEFAULT_SEPARATOR)
if (($splitIndex -lt 0) -or ($splitIndex -gt $abbreviateAfter))
{
# Find furthest space character that isn't greater than the end buffer
$spaceIndex = 0
while ($true)
{
$nextSpace = $Name.IndexOf(' ', $spaceIndex)
if ($nextSpace -eq -1)
{
break
}
if (($nextSpace + 1) -gt $abbreviateAfter)
{
break
}
$spaceIndex = $nextSpace + 1
}
$splitIndex = $spaceIndex
}
else
{
$splitIndex += $DEFAULT_SEPARATOR.Length
}
return @($Name.Substring(0, $splitIndex), $Name.Substring($splitIndex))
}
function Shorten-String
{
param(
[string]$Name,
[int]$MaxLength
)
if ($Name.Length -le $MaxLength)
{
return $Name
}
$splitString = Separate-String -Name $Name -MaxLength $MaxLength
$baseName = $splitString[0]
$reduceName = $splitString[1]
Write-Debug "Strip unnecessary char sequences"
$reduceName = Strip-Unnecessary -In $reduceName
if (($baseName + $reduceName).Length -le $MaxLength)
{
return ($baseName + $reduceName)
}
Write-Debug "Abbreviate extended name"
$reduceName = Abbreviate-String -In $reduceName
if (($baseName + $reduceName).Length -le $MaxLength)
{
return ($baseName + $reduceName)
}
Write-Debug "Strip spaces from extended name"
$reduceName = $reduceName -replace " ",""
if (($baseName + $reduceName).Length -le $MaxLength)
{
return ($baseName + $reduceName)
}
Write-Debug "Strip spaces from base name"
$baseName = $baseName -replace " ",""
if (($baseName + $reduceName).Length -le $MaxLength)
{
return ($baseName + $reduceName)
}
Write-Debug "Truncate as a last resort"
return ($baseName + $reduceName).Substring(0, $MaxLength)
}
function Ask-Continue
{
param (
[string]$Message = "Continue? (Y/N)"
)
$response = Read-Host -Prompt $Message
return $response -match '^(Y|y)$'
}
$toRename = @()
$nameDictionary = @{}
$dupeNames = [System.Collections.Generic.HashSet[string]]::new()
foreach ($file in $files)
{
$maxBaseLength = ($MAX_LENGTH - $file.Extension.Length)
$baseName = $file.BaseName
$newName = Shorten-String -Name $baseName -MaxLength $maxBaseLength
$newName += $file.Extension
$fileRename = @{
SourceFile = $file
NewName = $newName
}
if (!$nameDictionary.ContainsKey($newName))
{
$nameDictionary[$newName] = @($fileRename)
}
else
{
$nameDictionary[$newName] += $fileRename
$dupeNames.Add($newName) | Out-Null
}
$toRename += $fileRename
}
if ($dupeNames.Count -gt 0)
{
Write-Host -ForegroundColor Yellow "Found $($dupeNames.Count) duplicates as a result of rename:"
foreach ($dupeName in $dupeNames)
{
Write-Host -ForegroundColor Cyan "$($dupeName):"
foreach ($dupeResult in $nameDictionary[$dupeName])
{
Write-Host "`t-->$($dupeResult.SourceFile.FullName)"
}
}
Write-Host -ForegroundColor Yellow "Please resolve before operation can continue."
exit 1
}
Write-Host -ForegroundColor Cyan "Will rename $($toRename.Count) files:"
foreach ($r in $toRename)
{
Write-Host $r.SourceFile.FullName
Write-Host "`t--> ($($r.NewName.Length)) $($r.NewName)" -ForegroundColor Green
}
if (-not (Ask-Continue))
{
Write-Host "Abort"
exit 0
}
foreach ($r in $toRename)
{
Rename-Item -Path $r.SourceFile.FullName -NewName $r.NewName
}
$invalidChars = '.*[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!\#\$\%\&\''\(\)\-\.\@\[\]\^_`\{\}~ ].*'
function Ask-Continue
{
param (
[string]$Message = "Continue? (Y/N)"
)
$response = Read-Host -Prompt $Message
return $response -match '^(Y|y)$'
}
$files = (Get-ChildItem -File -Recurse | Where-Object { $_.BaseName -match $invalidChars })
foreach ($file in $files)
{
# Initialize an empty string to store the cleaned filename
$cleanedBaseName = ""
# Remove invalid characters
foreach ($char in $file.BaseName.ToCharArray()) {
if ($char -notmatch $invalidChars) {
$cleanedBaseName += $char
}
}
# Quick and dirty remove duplicate spaces
$cleanedBaseName = $cleanedBaseName -replace " ", " "
$cleanedBaseName = $cleanedBaseName -replace " ", " "
$cleanedBaseName = $cleanedBaseName.Trim()
# Re-add file extension
$cleanedBaseName += $file.Extension
Write-Host -ForegroundColor Cyan $file.FullName
Write-Host -ForegroundColor Green "`t-->$cleanedBaseName"
Rename-Item -Path $file.FullName -NewName $cleanedBaseName
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment