206 lines
14 KiB
PowerShell
206 lines
14 KiB
PowerShell
# 정식 판 업데이트 길 시험 (관리자 지시 10/20): 사용자 데이터 · 시작 메뉴 · 실제 GitHub 를 건드리지 않고 끝까지.
|
|
# 1. %TEMP%\jj_updtest_<표> 에 예전 판 배포 폴더 (-OldStage) 와 그 판이 남겼을 데이터 (설정 · 동영상 목록 · 비밀번호 (DPAPI secrets.dat,
|
|
# 가짜 서버 jj_updtest_dav) · 이 판이 모르는 설정 키) 를 만든다.
|
|
# 2. 통합 시험 integration_test\update_path_real_test.dart 가 앱 안 업데이트 길로 [지금 설치] (시험 길 JJ_UPDATE_LOCAL_ZIP = -NewZip).
|
|
# 3. 시험이 끝나면 앱의 교체 스크립트가 배포 폴더를 바꾸고 새 판을 띄운다 (따로 데이터라 시작 메뉴를 건드리지 않음 - 121dd43).
|
|
# 4. 새 판이 설정을 쓴 뒤 끄고 확인: 판 · 설정 · 모르는 키 · 동영상 목록 · 비밀번호 · 보관본 (비밀번호 없음) · 시작 메뉴 바로 가기 그대로.
|
|
# 5. 시험 폴더를 지우고 jj_updtest_* 가 0개인지 본다.
|
|
# 사용자 시작 메뉴를 지키는 두 겹 (관리자 지시 10/21, 28 지적):
|
|
# ① 시작 전에 -NewZip 안의 en.json 에 시작 메뉴 막기 (121dd43) 의 글이 있는지 본다 - 없으면 (10.20_001 과 그 전 판) 아무것도 하지 않고 멈춘다.
|
|
# 예전 판 (-OldStage) 은 켜지 않으므로 보지 않는다.
|
|
# ② 시작 전에 사용자 바로 가기 (.lnk 사본) 와 HKCU\Software\Classes\jjmkvmaker (reg export) 를 시험 폴더 밖 %TEMP%\jj_updtest_backup_<표> 에
|
|
# 보관하고, 끝에 (시험 폴더를 지우기 전) 다르면 되돌린 뒤 같은지 다시 보고 크게 알린다. 같으면 보관본을 지운다 (되돌리지 못하면 남김).
|
|
# 사용 (app 폴더에서): powershell -ExecutionPolicy Bypass -File tool\update_path_check.ps1 -NewZip <zip> [-OldStage <폴더>] [-From 2026.10.18_001]
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$NewZip,
|
|
[string]$OldStage = '',
|
|
[string]$From = '2026.10.18_001',
|
|
[int]$WaitSeconds = 60
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
$appSrc = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
|
$root = Split-Path $appSrc # 앱 폴더 (app · wt_*) 의 위 = 루트
|
|
if (-not $OldStage) { $OldStage = Join-Path $root "release\stage_v$From\JJ_MKVMaker" }
|
|
$NewZip = (Resolve-Path $NewZip).Path
|
|
if ((Split-Path $NewZip -Leaf) -notmatch '_v(\d{4}\.\d{2}\.\d{2}_\d{3})_win64\.zip$') { throw "zip 이름에서 판을 읽지 못함: $NewZip" }
|
|
$newVer = $Matches[1]
|
|
$sha = (Get-FileHash $NewZip -Algorithm SHA256).Hash.ToLower()
|
|
$fails = New-Object System.Collections.Generic.List[string]
|
|
function Check([string]$what, [bool]$ok, [string]$detail = '') {
|
|
if ($ok) { Write-Host "통과: $what $detail" } else { Write-Host "실패: $what $detail"; $fails.Add($what) }
|
|
}
|
|
|
|
# 사용자 쪽 (읽기만): 시작 메뉴 바로 가기 · 주소 연결 · %TEMP% 의 jj 이름
|
|
$lnk = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs\JJ_MKVMaker.lnk'
|
|
function UserArea {
|
|
$t = if (Test-Path $lnk) { (New-Object -ComObject WScript.Shell).CreateShortcut($lnk).TargetPath + ' @' + (Get-Item $lnk).LastWriteTime.Ticks } else { '(없음)' }
|
|
$k = 'HKCU:\Software\Classes\jjmkvmaker\shell\open\command'
|
|
$r = if (Test-Path $k) { (Get-ItemProperty $k).'(default)' } else { '(없음)' }
|
|
"$t | $r"
|
|
}
|
|
$userBefore = UserArea
|
|
$sysTemp = [IO.Path]::GetTempPath()
|
|
$tempBefore = @(Get-ChildItem $sysTemp -Force -Filter 'jj*' -ErrorAction SilentlyContinue | ForEach-Object { $_.Name })
|
|
|
|
# ① 새 판에 시작 메뉴 막기가 있는지 (번역 글로 본다 - 121dd43 에서 생긴 글)
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
$marker = 'Started with a test data folder (JJ_MKVMAKER_DATA)'
|
|
$zf = [IO.Compression.ZipFile]::OpenRead($NewZip)
|
|
try {
|
|
$en = $zf.Entries | Where-Object { $_.FullName -like '*/data/flutter_assets/assets/l10n/en.json' } | Select-Object -First 1
|
|
$hasGuard = $false
|
|
if ($en) { $sr = New-Object IO.StreamReader($en.Open()); try { $hasGuard = $sr.ReadToEnd().Contains($marker) } finally { $sr.Dispose() } }
|
|
} finally { $zf.Dispose() }
|
|
if (-not $hasGuard) {
|
|
Write-Host "멈춤: $NewZip 에는 시작 메뉴 막기 (121dd43) 가 없습니다 - 이 판을 따로 데이터로 켜면 사용자 시작 메뉴 바로 가기 · jjmkvmaker:// 가 바뀝니다. 아무것도 하지 않았습니다."
|
|
exit 3
|
|
}
|
|
Write-Host '통과: 새 판에 시작 메뉴 막기가 있음 (en.json)'
|
|
|
|
$tag = -join ((1..6) | ForEach-Object { '{0:x}' -f (Get-Random -Maximum 16) })
|
|
# ② 사용자 바로 가기 · 주소 연결 보관 (시험 폴더 밖)
|
|
$B = Join-Path $sysTemp "jj_updtest_backup_$tag"
|
|
$protoKey = 'HKCU\Software\Classes\jjmkvmaker'
|
|
New-Item -ItemType Directory $B | Out-Null
|
|
$hadLnk = Test-Path $lnk
|
|
if ($hadLnk) { Copy-Item -LiteralPath $lnk "$B\JJ_MKVMaker.lnk" }
|
|
$hadProto = Test-Path 'HKCU:\Software\Classes\jjmkvmaker'
|
|
if ($hadProto) {
|
|
cmd /c "reg export `"$protoKey`" `"$B\jjmkvmaker.reg`" /y >nul 2>&1"
|
|
if ($LASTEXITCODE -ne 0) { throw "주소 연결을 보관하지 못함 (reg export $LASTEXITCODE) - 시작하지 않음" }
|
|
}
|
|
Write-Host "사용자 바로 가기 · 주소 연결 보관: $B (바로 가기 $hadLnk · 주소 연결 $hadProto)"
|
|
$T = Join-Path $sysTemp "jj_updtest_$tag"
|
|
$app = "$T\app"; $data = "$T\data"; $media = "$T\media"; $tmp = "$T\temp"
|
|
New-Item -ItemType Directory $T, $data, $media, $tmp | Out-Null
|
|
Write-Host "시험 폴더: $T ($From → $newVer)"
|
|
$appProcs = { @(Get-Process jj_mkvmaker -ErrorAction SilentlyContinue | Where-Object { $_.Path -and $_.Path.StartsWith($T, [StringComparison]::OrdinalIgnoreCase) }) }
|
|
try {
|
|
# 1. 예전 판 배포 폴더 · 데이터
|
|
robocopy $OldStage $app /E /NFL /NDL /NJH /NJS /NP | Out-Null
|
|
if ($LASTEXITCODE -ge 8) { throw "예전 판 복사 실패 $LASTEXITCODE" }
|
|
$oldVer = (Get-Item "$app\Lib\jj_mkvmaker.exe").VersionInfo.ProductVersion
|
|
$videos = @("$media\a.mp4", "$media\b.mkv")
|
|
foreach ($v in $videos) { [IO.File]::WriteAllBytes($v, [byte[]](1..64)) }
|
|
$pw = "jj_updtest-Pw-$tag"
|
|
$utf8 = New-Object Text.UTF8Encoding $false
|
|
[IO.File]::WriteAllText("$data\videos.json", (ConvertTo-Json -InputObject $videos -Compress), $utf8)
|
|
$settings = [ordered]@{
|
|
lastRunVersion = $From
|
|
autoCheckUpdates = $false
|
|
startMenuShortcut = $true
|
|
webdavServers = @(@{ id = 'jj_updtest_dav'; name = 'jj_updtest_dav'; url = 'https://127.0.0.1:9/dav'; user = 'jj_updtest_user' })
|
|
zz_jj_updtest_future = @{ keep = $true; n = 7 }
|
|
}
|
|
[IO.File]::WriteAllText("$data\settings.json", (ConvertTo-Json -InputObject $settings -Depth 5), $utf8)
|
|
Add-Type -AssemblyName System.Security
|
|
$plain = $utf8.GetBytes((ConvertTo-Json -InputObject @{ 'dav.pw.jj_updtest_dav' = $pw } -Compress))
|
|
[IO.File]::WriteAllBytes("$data\secrets.dat", [Security.Cryptography.ProtectedData]::Protect($plain, $null, 'CurrentUser'))
|
|
|
|
# 2. 앱 안 업데이트 길 (통합 시험) - 환경 변수는 이 시험 · 새 판에만
|
|
$saved = @{}
|
|
$vars = @{ JJ_MKVMAKER_DATA = $data; JJ_UPDATE_LOCAL_ZIP = "$NewZip;$sha"; JJ_UPD_APP = $app; JJ_UPD_FROM = $From; TEMP = $tmp; TMP = $tmp }
|
|
foreach ($k in $vars.Keys) { $saved[$k] = [Environment]::GetEnvironmentVariable($k); [Environment]::SetEnvironmentVariable($k, $vars[$k]) }
|
|
Push-Location $appSrc
|
|
try {
|
|
# flutter 의 경고 (stderr) 를 오류로 보지 않게 (PowerShell 5.1 의 2>&1 + Stop)
|
|
$ErrorActionPreference = 'Continue'
|
|
$out = & flutter test integration_test\update_path_real_test.dart -d windows 2>&1 | ForEach-Object { "$_" }
|
|
$code = $LASTEXITCODE
|
|
$ErrorActionPreference = 'Stop'
|
|
} finally {
|
|
Pop-Location
|
|
foreach ($k in $saved.Keys) { [Environment]::SetEnvironmentVariable($k, $saved[$k]) }
|
|
}
|
|
$out | Select-String 'UPD_SCHEDULED|All tests|Some tests|\[E\]|Expected|Actual' | ForEach-Object { Write-Host " $_" }
|
|
Check '앱 안 업데이트 길 ([지금 설치] · 설정 보관 · 교체 예약)' ($code -eq 0 -and ($out -match 'UPD_SCHEDULED')) "(exit $code)"
|
|
|
|
# 3. 교체 → 새 판이 켜짐 → 설정을 씀
|
|
# flutter test 는 시험이 끝나면 시험 앱과 그 앱이 띄운 프로세스 (교체 스크립트 포함) 를 함께 끝낸다 (15 확인 10/21: 기록 없음 · 도는 것 0).
|
|
# 사용자 판은 앱이 스스로 끝나 그런 일이 없다. 그래서 앱이 써 둔 교체 스크립트를 앱이 넘긴 것과 같은 인수 · 환경으로 여기서 띄운다
|
|
$exe = "$app\Lib\jj_mkvmaker.exe"
|
|
$work = "$T\upd_tmp"
|
|
$ps1 = "$work\jj_mkvmaker_update.ps1"
|
|
$src = Get-ChildItem "$work\jj_mkvmaker_update_$newVer\extract" -Recurse -Filter jj_mkvmaker.exe -ErrorAction SilentlyContinue |
|
|
Sort-Object { $_.FullName.Split('\').Count } | Select-Object -First 1
|
|
$testPid = if (($out -join "`n") -match 'UPD_SCHEDULED pid=(\d+)') { [int]$Matches[1] } else { 0 }
|
|
Check '앱이 교체 스크립트 · 풀어 둔 새 판을 남김' ((Test-Path $ps1) -and $src) "($ps1)"
|
|
if ((Test-Path $ps1) -and $src) {
|
|
foreach ($k in $vars.Keys) { [Environment]::SetEnvironmentVariable($k, $vars[$k]) }
|
|
try {
|
|
Start-Process powershell -WindowStyle Hidden -ArgumentList '-NoProfile', '-ExecutionPolicy', 'Bypass', '-WindowStyle', 'Hidden', '-File', $ps1,
|
|
'-ParentPid', $testPid, '-Source', $src.DirectoryName, '-Target', $app, '-Exe', "$app\jj_mkvmaker.exe" | Out-Null
|
|
} finally {
|
|
foreach ($k in $saved.Keys) { [Environment]::SetEnvironmentVariable($k, $saved[$k]) }
|
|
}
|
|
}
|
|
$sw = [Diagnostics.Stopwatch]::StartNew()
|
|
while ($sw.Elapsed.TotalSeconds -lt 120 -and -not ((Get-Item $exe).VersionInfo.ProductVersion -ne $oldVer -and (& $appProcs).Count -gt 0)) { Start-Sleep -Milliseconds 500 }
|
|
$newPv = (Get-Item $exe).VersionInfo.ProductVersion
|
|
# 교체 스크립트의 흔적 (안 됐을 때 까닭을 보려고): 기록 · 아직 도는 스크립트
|
|
$ulog = "$app\..\upd_tmp\jj_mkvmaker_update.log"
|
|
Write-Host (" 교체 기록: " + $(if (Test-Path $ulog) { ((Get-Content $ulog | Select-String 'Copied|Extras|Failed|ERROR' | Select-Object -First 4) -join ' | ') } else { '(없음 - 스크립트가 robocopy 까지 가지 않음)' }))
|
|
$ps = @(Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" | Where-Object { $_.CommandLine -like '*jj_mkvmaker_update.ps1*' -and $_.CommandLine -like "*$T*" })
|
|
Write-Host " 아직 도는 교체 스크립트: $($ps.Count)개"
|
|
Check '프로그램 교체' ($newPv -ne $oldVer) "($oldVer → $newPv)"
|
|
Check '새 판이 저절로 켜짐 (교체 스크립트)' ((& $appProcs).Count -gt 0)
|
|
$sw.Restart()
|
|
$s = $null
|
|
while ($sw.Elapsed.TotalSeconds -lt $WaitSeconds) {
|
|
try { $s = Get-Content -Raw -Encoding UTF8 "$data\settings.json" | ConvertFrom-Json } catch { $s = $null }
|
|
if ($s -and $s.lastRunVersion -eq $newVer) { break }
|
|
Start-Sleep -Milliseconds 500
|
|
}
|
|
Start-Sleep -Seconds 3
|
|
& $appProcs | ForEach-Object { $_.CloseMainWindow() | Out-Null }
|
|
Start-Sleep -Seconds 5
|
|
& $appProcs | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
|
|
# 4. 확인
|
|
$raw = Get-Content -Raw -Encoding UTF8 "$data\settings.json"
|
|
$s = $raw | ConvertFrom-Json
|
|
Check '판 기록 (lastRunVersion)' ($s.lastRunVersion -eq $newVer) "($($s.lastRunVersion))"
|
|
Check '이 판이 모르는 설정 키 그대로' ($s.zz_jj_updtest_future.keep -eq $true -and $s.zz_jj_updtest_future.n -eq 7)
|
|
Check 'WebDAV 서버 그대로' (@($s.webdavServers | Where-Object { $_.id -eq 'jj_updtest_dav' -and $_.user -eq 'jj_updtest_user' }).Count -eq 1)
|
|
$v2 = Get-Content -Raw -Encoding UTF8 "$data\videos.json" | ConvertFrom-Json
|
|
Check '동영상 목록 그대로' ((@($v2) -join '|') -eq ($videos -join '|')) "($(@($v2).Count)개)"
|
|
$dec = $utf8.GetString([Security.Cryptography.ProtectedData]::Unprotect([IO.File]::ReadAllBytes("$data\secrets.dat"), $null, 'CurrentUser')) | ConvertFrom-Json
|
|
Check '비밀번호 (안전 저장소) 그대로' ($dec.'dav.pw.jj_updtest_dav' -eq $pw)
|
|
$plainHits = @(Get-ChildItem $data -Recurse -File | Where-Object { $_.Name -ne 'secrets.dat' -and (Select-String -LiteralPath $_.FullName -SimpleMatch $pw -Quiet) } | ForEach-Object { $_.FullName.Substring($data.Length + 1) })
|
|
Check '평문 비밀번호 없음 (설정 · .bak · 보관본 · 기록)' ($plainHits.Count -eq 0) ($plainHits -join ', ')
|
|
Check "예전 판 ($From) 설정 보관본" (Test-Path "$data\version_snapshots\$From\settings.json")
|
|
$userAfter = UserArea
|
|
Check '사용자 시작 메뉴 바로 가기 · jjmkvmaker:// 그대로' ($userAfter -eq $userBefore) "($userAfter)"
|
|
} finally {
|
|
& $appProcs | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
# ② 바뀌었으면 되돌림 (시험 폴더를 지우기 전) - 같은지 다시 보고, 같을 때만 보관본을 지운다
|
|
$now = UserArea
|
|
if ($now -ne $userBefore) {
|
|
Write-Host '!!!!!!!! 사용자 시작 메뉴 바로 가기 · jjmkvmaker:// 가 바뀌었습니다 - 보관본으로 되돌립니다 !!!!!!!!'
|
|
Write-Host " 시험 전: $userBefore"
|
|
Write-Host " 시험 뒤: $now"
|
|
if ($hadLnk) { Copy-Item -LiteralPath "$B\JJ_MKVMaker.lnk" $lnk -Force } elseif (Test-Path $lnk) { Remove-Item -LiteralPath $lnk -Force }
|
|
# reg 의 오류 글은 cmd 안에서 버린다 (PowerShell 의 2>$null 은 Stop 에서 예외가 됨)
|
|
cmd /c "reg delete `"$protoKey`" /f >nul 2>&1"
|
|
if ($hadProto) { cmd /c "reg import `"$B\jjmkvmaker.reg`" >nul 2>&1" }
|
|
$restored = UserArea
|
|
# 되돌린 바로 가기는 고친 시각이 보관본 것과 같아야 한다 (Copy-Item 은 고친 시각을 그대로 둔다)
|
|
Check '되돌린 뒤 사용자 바로 가기 · 주소 연결이 시험 전과 같음' ($restored -eq $userBefore) "($restored)"
|
|
if ($restored -ne $userBefore) { Write-Host " 보관본을 남깁니다: $B" }
|
|
$fails.Add('사용자 시작 메뉴 · 주소 연결이 시험 중 바뀜 (되돌림)')
|
|
}
|
|
if ((UserArea) -eq $userBefore) { cmd /c "rd /s /q `"$B`" 2>nul" }
|
|
for ($i = 0; $i -lt 30 -and (Test-Path -LiteralPath $T); $i++) {
|
|
if ($i -gt 0) { Start-Sleep -Seconds 2 }
|
|
cmd /c "rd /s /q `"\\?\$T`" 2>nul"
|
|
}
|
|
$left = @(Get-ChildItem $sysTemp -Force -Filter 'jj_updtest_*' -ErrorAction SilentlyContinue)
|
|
Check '시험 폴더 · 가짜 자격 증명 (jj_updtest_*) 0개' ($left.Count -eq 0) "($($left.Count)개)"
|
|
$tempNew = @(Get-ChildItem $sysTemp -Force -Filter 'jj*' -ErrorAction SilentlyContinue | ForEach-Object { $_.Name } | Where-Object { $tempBefore -notcontains $_ -and $_ -notlike 'jj_updtest_*' })
|
|
# 참고만: 시험 · 새 판의 TEMP 는 시험 폴더로 돌렸으므로, 여기 새로 생긴 jj 이름은 다른 세션의 시험일 수 있다
|
|
if ($tempNew.Count -gt 0) { Write-Host "참고 - %TEMP% 에 새로 생긴 jj 이름 (다른 세션일 수 있음): $($tempNew -join ', ')" }
|
|
}
|
|
if ($fails.Count -gt 0) { Write-Host "실패 $($fails.Count)개"; exit 1 }
|
|
Write-Host '업데이트 길: 모두 통과'
|
|
exit 0
|