Last active
September 9, 2025 19:35
-
-
Save miiiladiii244/e13f6bf01e84e0d7353fca7124fbd95c to your computer and use it in GitHub Desktop.
Check for existence of a list of files under a directory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Fast existence check for files listed in a text/playlist against a folder tree. | |
| .DESCRIPTION | |
| - Indexes all files under -RootPath (default: current folder) once. | |
| - For each line in -ListPath, checks existence by: | |
| 1) Absolute path match | |
| 2) Relative path match (case-insensitive) within the root tree | |
| 3) Optional leaf-name fallback anywhere in the tree (-AllowLeafFallback) | |
| Skips comments (#...) and URLs. Writes missing lines to an output file. | |
| .PARAMETER ListPath | |
| Path to the playlist/file list (e.g., .pla, .m3u, .txt). | |
| .PARAMETER RootPath | |
| Folder to search under (defaults to current directory). | |
| .PARAMETER OutputPath | |
| File to write missing lines to (defaults to "missing-files.txt" under RootPath). | |
| .PARAMETER AllowLeafFallback | |
| If set, a line is considered found if a file with the same leaf name exists anywhere under RootPath. | |
| .EXAMPLE | |
| PS> Check-FilesIndexed.ps1 -ListPath "C:\Lists\New Playlist.pla" | |
| .EXAMPLE | |
| PS> Check-FilesIndexed.ps1 -ListPath ".\mylist.txt" -RootPath "D:\Media" -AllowLeafFallback | |
| #> | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $true, Position = 0)] | |
| [string]$ListPath, | |
| [Parameter(Mandatory = $false)] | |
| [string]$RootPath = (Get-Location).ProviderPath, | |
| [Parameter(Mandatory = $false)] | |
| [string]$OutputPath = $(Join-Path -Path ((Get-Item -LiteralPath $RootPath).FullName) -ChildPath "missing-files.txt"), | |
| [switch]$AllowLeafFallback | |
| ) | |
| # ---- Guards ---- | |
| if (-not (Test-Path -LiteralPath $ListPath -PathType Leaf)) { | |
| Write-Error "List file not found: $ListPath" | |
| exit 1 | |
| } | |
| if (-not (Test-Path -LiteralPath $RootPath -PathType Container)) { | |
| Write-Error "Root path folder not found: $RootPath" | |
| exit 1 | |
| } | |
| # ---- Helpers ---- | |
| function Normalize-InputPath([string]$s) { | |
| if (-not $s) { return $null } | |
| $t = $s.Trim() | |
| if ($t -eq "") { return $null } | |
| if ($t.StartsWith("#")) { return $null } # comment | |
| if ($t -match "^[a-zA-Z]+://") { return $null } # URL | |
| # strip outer quotes | |
| if (($t.StartsWith('"') -and $t.EndsWith('"')) -or ($t.StartsWith("'") -and $t.EndsWith("'"))) { | |
| $t = $t.Substring(1, $t.Length - 2) | |
| } | |
| # expand env vars like %USERPROFILE% | |
| $t = [Environment]::ExpandEnvironmentVariables($t) | |
| # unify separators and trim leading .\ or ./ and leading slashes | |
| $t = $t -replace '/', '\' | |
| if ($t -like ".\*") { $t = $t.Substring(2) } | |
| $t = $t.TrimStart('\') | |
| return $t | |
| } | |
| # Compute relative path of a full path to the RootPath (case-insensitive) | |
| function To-Relative([string]$full, [string]$root) { | |
| $rootNorm = $root.TrimEnd('\') | |
| if ($full.StartsWith($rootNorm, [System.StringComparison]::OrdinalIgnoreCase)) { | |
| $rel = $full.Substring($rootNorm.Length).TrimStart('\') | |
| return $rel | |
| } | |
| return $null | |
| } | |
| # ---- Index the tree under RootPath ---- | |
| Write-Verbose "Indexing files under: $RootPath" | |
| $allFiles = Get-ChildItem -LiteralPath $RootPath -Recurse -File -ErrorAction SilentlyContinue | |
| # HashSet of full paths (lowercased) and relative paths (lowercased) | |
| $fullSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) | |
| $relSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) | |
| # Dictionary of leaf name -> count (case-insensitive) | |
| $leafDict = New-Object 'System.Collections.Generic.Dictionary[string,int]' ([System.StringComparer]::OrdinalIgnoreCase) | |
| $rootFull = (Get-Item -LiteralPath $RootPath).FullName | |
| foreach ($f in $allFiles) { | |
| $fullSet.Add($f.FullName) | Out-Null | |
| $rel = To-Relative -full $f.FullName -root $rootFull | |
| if ($rel) { $relSet.Add($rel) | Out-Null } | |
| $leaf = $f.Name | |
| if ($leafDict.ContainsKey($leaf)) { $leafDict[$leaf]++ } else { $leafDict[$leaf] = 1 } | |
| } | |
| # ---- Process the list ---- | |
| $missing = New-Object System.Collections.Generic.List[string] | |
| foreach ($raw in Get-Content -LiteralPath $ListPath -Encoding UTF8) { | |
| $p = Normalize-InputPath $raw | |
| if (-not $p) { continue } | |
| $exists = $false | |
| if ([System.IO.Path]::IsPathRooted($p)) { | |
| # Absolute path: direct hit or relative-to-root appearance | |
| if ($fullSet.Contains($p)) { | |
| $exists = $true | |
| } else { | |
| $relCandidate = To-Relative -full $p -root $rootFull | |
| if ($relCandidate -and $relSet.Contains($relCandidate)) { | |
| $exists = $true | |
| } | |
| } | |
| } else { | |
| # Relative path: exact relative match or combined path match | |
| if ($relSet.Contains($p)) { | |
| $exists = $true | |
| } else { | |
| $candidate = Join-Path -Path $rootFull -ChildPath $p | |
| if ($fullSet.Contains($candidate)) { | |
| $exists = $true | |
| } | |
| } | |
| } | |
| if (-not $exists -and $AllowLeafFallback) { | |
| try { | |
| $leaf = [System.IO.Path]::GetFileName($p) | |
| if ($leaf -and $leafDict.ContainsKey($leaf) -and $leafDict[$leaf] -ge 1) { | |
| $exists = $true | |
| } | |
| } catch { } | |
| } | |
| if (-not $exists) { | |
| $missing.Add($raw) | Out-Null # keep original line for clarity | |
| } | |
| } | |
| # ---- Output ---- | |
| if ($missing.Count -eq 0) { | |
| Write-Host "All listed files were found under '$RootPath'." -ForegroundColor Green | |
| } else { | |
| $missing | Set-Content -LiteralPath $OutputPath -Encoding UTF8 | |
| Write-Host "Missing files ($($missing.Count)):" -ForegroundColor Yellow | |
| $missing | ForEach-Object { Write-Host " $_" } | |
| Write-Host "`nSaved to: $OutputPath" -ForegroundColor Yellow | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Search under the current folder
Check-FilesIndexed.ps1 -ListPath "C:\Users\miiiladiii\Music\New Playlist.pla"
Search under a specific root
Check-FilesIndexed.ps1 -ListPath ".\list.txt" -RootPath "D:\Media"
Allow loose leaf-name fallback (treat 'song.mp3' as found if any 'song.mp3' exists anywhere under the root)
Check-FilesIndexed.ps1 -ListPath ".\list.txt" -AllowLeafFallback