CVR Manager 1.0.0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
<#
|
||||
One-off migration: give every install folder the 4-character id suffix again.
|
||||
|
||||
before: <library>\installs\Public Scripting\
|
||||
after: <library>\installs\Public Scripting-9ce8\ (suffix = first 4 characters of the install's Id in config.json)
|
||||
|
||||
Run it after 2026-10-05-flatten-install-layout.ps1 (which produced names without a suffix).
|
||||
With the suffix, two installs may share a name without their folders colliding.
|
||||
|
||||
Dry run by default. Run with pwsh.
|
||||
pwsh -File 2026-10-05-add-folder-id-suffix.ps1 # show the plan
|
||||
pwsh -File 2026-10-05-add-folder-id-suffix.ps1 -Apply
|
||||
|
||||
Close CVRManager and every game from the library first. config.json is backed up next to itself,
|
||||
and progress is saved after every install, so a folder held open by Explorer only blocks that install.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ConfigDir = (Join-Path $env:APPDATA 'CVRManager'),
|
||||
[switch]$Apply,
|
||||
[switch]$SkipAppCheck # only for testing against a throwaway copy
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Get-SafeFolderName([string]$name) {
|
||||
$bad = [IO.Path]::GetInvalidFileNameChars()
|
||||
$clean = -join ($name.ToCharArray() | ForEach-Object { if ($bad -contains $_) { ' ' } else { $_ } })
|
||||
while ($clean.Contains(' ')) { $clean = $clean.Replace(' ', ' ') }
|
||||
$clean = $clean.Trim().TrimEnd('.')
|
||||
if ($clean.Length -gt 60) { $clean = $clean.Substring(0, 60).TrimEnd() }
|
||||
if ($clean.Length -eq 0) { 'Install' } else { $clean }
|
||||
}
|
||||
|
||||
$configPath = Join-Path $ConfigDir 'config.json'
|
||||
if (-not (Test-Path $configPath)) { throw "No config at $configPath" }
|
||||
$cfg = Get-Content $configPath -Raw | ConvertFrom-Json
|
||||
$lib = if ($cfg.LibraryRoot) { $cfg.LibraryRoot } else { Join-Path $env:LOCALAPPDATA 'CVRManager' }
|
||||
$installs = Join-Path $lib 'installs'
|
||||
|
||||
$running = @(if (-not $SkipAppCheck) { Get-Process CVRManager -ErrorAction SilentlyContinue }) + @(Get-Process ChilloutVR -ErrorAction SilentlyContinue | Where-Object { $_.Path -and $_.Path.StartsWith($installs, [StringComparison]::OrdinalIgnoreCase) })
|
||||
if ($Apply -and $running.Count -gt 0) { throw "Close CVRManager and any game started from the library first (running: $(($running | ForEach-Object { $_.ProcessName + ' ' + $_.Id }) -join ', '))." }
|
||||
|
||||
$all = @($cfg.Installs)
|
||||
$plans = @()
|
||||
foreach ($i in $all) {
|
||||
$stem = "$(Get-SafeFolderName $i.Name)-$($i.Id.Substring(0, 4))"
|
||||
$want = $stem; $n = 2
|
||||
while ($true) {
|
||||
$clash = $all | Where-Object { $_ -ne $i -and $_.Folder -ieq $want }
|
||||
$existsOther = ($want -ine $i.Folder) -and (Test-Path (Join-Path $installs $want))
|
||||
if (-not $clash -and -not $existsOther) { break }
|
||||
$want = "$stem-$n"; $n++
|
||||
}
|
||||
if ($want -cne $i.Folder) { $plans += [pscustomobject]@{ Install = $i; From = $i.Folder; To = $want } }
|
||||
}
|
||||
|
||||
"Library: $lib"
|
||||
if ($plans.Count -eq 0) { 'Nothing to do: every install folder already has its suffix.'; return }
|
||||
"Planned renames:"
|
||||
foreach ($p in $plans) { " - $($p.Install.Name): '$($p.From)' -> '$($p.To)'" + $(if (-not (Test-Path (Join-Path $installs $p.From))) { ' (folder missing on disk: config entry only)' } else { '' }) }
|
||||
if (-not $Apply) { ''; 'Dry run only. Re-run with -Apply to make these changes.'; return }
|
||||
|
||||
$backup = "$configPath.bak-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
|
||||
Copy-Item $configPath $backup
|
||||
"Config backed up to $backup"
|
||||
|
||||
function Save-Config { [IO.File]::WriteAllText($configPath, ($cfg | ConvertTo-Json -Depth 30), (New-Object Text.UTF8Encoding($false))) }
|
||||
$failed = @()
|
||||
foreach ($p in $plans) {
|
||||
$oldDir = Join-Path $installs $p.From
|
||||
$newDir = Join-Path $installs $p.To
|
||||
try {
|
||||
if (Test-Path $oldDir) {
|
||||
if ($oldDir -ieq $newDir) { $tmp = "$oldDir.renaming"; [IO.Directory]::Move($oldDir, $tmp); [IO.Directory]::Move($tmp, $newDir) }
|
||||
else { [IO.Directory]::Move($oldDir, $newDir) }
|
||||
}
|
||||
$p.Install.Folder = $p.To
|
||||
Save-Config
|
||||
" done: $($p.Install.Name) -> $newDir"
|
||||
}
|
||||
catch [UnauthorizedAccessException], [IO.IOException] {
|
||||
$failed += $p.Install.Name
|
||||
Write-Warning "$($p.Install.Name): $($_.Exception.Message)"
|
||||
Write-Warning 'A folder of this install is probably open in Explorer or another program. Close it and run the script again.'
|
||||
}
|
||||
}
|
||||
if ($failed.Count -gt 0) { "Finished with problems for: $($failed -join ', '). Everything else was renamed and saved." } else { 'Config updated. Start CVRManager again.' }
|
||||
Reference in New Issue
Block a user