# Second run (audit follow-up): migration from a 3.0 folder, path-finder commands pasted into PowerShell, # Chocolatey on a clean profile, refusal while GIMP runs. # PhotoGIMP on a real Windows machine: installs GIMP 3.2.6, applies PhotoGIMP the ways users do # (wrong folder, README way, our repair, upstream master), uninstalls it, and tests the Chocolatey # package. Every step writes facts and screenshots to verify/$env:VERIFY_OUT/. $OutName = $(if ($env:VERIFY_OUT) { $env:VERIFY_OUT } else { 'results' }) . "$PSScriptRoot\lib.ps1" $GimpUrl = 'https://download.gimp.org/gimp/v3.2/windows/gimp-3.2.6-setup.exe' $GimpSha = '9337cccbc01d4098ee7a3dab215b3afbe6ece99c5287c92441d6f12cf541ebca' $PgUrl = 'https://github.com/Diolinux/PhotoGIMP/releases/download/3.1/PhotoGIMP.zip' $PgSha = 'de6a17ffb70000051372d84141b7e69fc2e60a0af3bd3b3100c9afd5e0ff3ae6' $MasterCommit = 'fc61a8f' $Image = Join-Path $repo 'assets\moraine-lake.jpg' $Desk = [Environment]::GetFolderPath('Desktop') $Cfg = Join-Path $env:APPDATA 'GIMP' $facts.os = (Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, BuildNumber, OSArchitecture) $facts.arch = $env:PROCESSOR_ARCHITECTURE $facts.runner = $env:VERIFY_OUT $facts.date = (Get-Date).ToString('yyyy-MM-dd HH:mm') Set-Screen; Dismiss-Oobe; Hide-Consoles foreach ($e in $UIA::RootElement.FindAll([System.Windows.Automation.TreeScope]::Children, (New-Object System.Windows.Automation.PropertyCondition($UIA::NameProperty, 'Performance Options')))) { try { $e.GetCurrentPattern([System.Windows.Automation.WindowPattern]::Pattern).Close(); Log 'closed Performance Options' } catch { } } function Step($n) { Log "===== $n =====" } function Tree($p) { if (Test-Path $p) { Get-ChildItem -Force $p | ForEach-Object { $_.Name } } else { @() } } function Hash($p) { if (Test-Path $p) { (Get-FileHash $p -Algorithm SHA256).Hash.ToLower() } else { $null } } $script:GimpExe = $null function Start-Gimp([string]$tag, [switch]$WithImage, [int]$Settle = 25, [switch]$Splash) { $a = @('--console-messages'); if ($WithImage) { $a += "`"$Image`"" } $err = Join-Path $out "$tag-stderr.txt"; $o = Join-Path $out "$tag-stdout.txt" $p = Start-Process -FilePath $script:GimpExe -ArgumentList $a -PassThru -RedirectStandardError $err -RedirectStandardOutput $o $t0 = Get-Date if ($Splash) { foreach ($s in 2, 4, 6, 9) { while (((Get-Date) - $t0).TotalSeconds -lt $s) { Start-Sleep -Milliseconds 200 }; Shot "$tag-splash-${s}s" } } $end = (Get-Date).AddSeconds(300) while ((Get-Date) -lt $end) { $p.Refresh() if ($p.HasExited) { Log "$tag GIMP exited early with code $($p.ExitCode)"; break } if ($p.MainWindowHandle -ne 0 -and ($p.MainWindowTitle -match ([string][char]0x2013 + ' GIMP$') -or $p.MainWindowTitle -eq 'GNU Image Manipulation Program')) { break } Start-Sleep 1 } Log ("$tag main window after {0:N0}s: '{1}'" -f ((Get-Date) - $t0).TotalSeconds, $p.MainWindowTitle) Start-Sleep $Settle Hide-Consoles Close-Welcome $tag if ($p.MainWindowHandle -ne 0) { [N]::ShowWindow($p.MainWindowHandle, 3) | Out-Null; [N]::SetForegroundWindow($p.MainWindowHandle) | Out-Null; Start-Sleep 3 } return $p } $script:KeepWelcome = $false function Close-Welcome([string]$tag) { $hs = [N]::Find('Welcome to GIMP') Log "$tag welcome windows found: $($hs.Count)" if ($hs.Count -gt 0) { $facts["welcome_$tag"] = $true if ($script:KeepWelcome) { return } $h = $hs[0] [N]::SetForegroundWindow($h) | Out-Null; Start-Sleep 1 Key @(0x1B); Start-Sleep 2 if ([N]::Find('Welcome to GIMP').Count -gt 0) { $r = New-Object N+RECT if ([N]::GetWindowRect($h, [ref]$r)) { Click ($r.Right - 56) ($r.Bottom - 31); Log "$tag clicked Close at $($r.Right - 56),$($r.Bottom - 31)"; Start-Sleep 2 } } if ([N]::Find('Welcome to GIMP').Count -gt 0) { [N]::PostMessage($h, 0x0010, [IntPtr]::Zero, [IntPtr]::Zero) | Out-Null; Start-Sleep 2 } Log "$tag welcome closed: $([N]::Find('Welcome to GIMP').Count -eq 0)" return } $w = $null foreach ($e in $UIA::RootElement.FindAll([System.Windows.Automation.TreeScope]::Children, [System.Windows.Automation.Condition]::TrueCondition)) { if ($e.Current.Name -like 'Welcome to GIMP*') { $w = $e; break } } if (-not $w) { return } $facts["welcome_$tag"] = $true if ($script:KeepWelcome) { return } Shot "$tag-welcome" $b = FindName 'Close' 5 $w if ($b -and (Invoke-El $b)) { Log "$tag closed Welcome dialog"; Start-Sleep 2; return } try { $w.GetCurrentPattern([System.Windows.Automation.WindowPattern]::Pattern).Close(); Log "$tag closed Welcome window" } catch { Log "$tag could not close Welcome: $_" } Start-Sleep 2 } function Stop-Gimp($p, [string]$tag) { if (-not $p -or $p.HasExited) { return } $p.Refresh() if ($p.MainWindowHandle -ne 0) { [N]::SetForegroundWindow($p.MainWindowHandle) | Out-Null; Start-Sleep 1; Key @(0x11, 0x51) } if (-not $p.WaitForExit(20000)) { $p.CloseMainWindow() | Out-Null } if (-not $p.WaitForExit(20000)) { Log "$tag GIMP did not close in 30s; looking for a quit dialog" $q = FindName 'Quit' 3; if ($q) { Invoke-El $q | Out-Null; $p.WaitForExit(15000) | Out-Null } } if (-not $p.HasExited) { Log "$tag killing GIMP"; Stop-Process -Id $p.Id -Force } Get-Process -Name 'gimp*' -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep 3 } function Set-NoWelcome($dir) { # Same effect as unticking "Show on Start" in GIMP's Welcome dialog; only for clean screenshots. $rc = Join-Path $dir 'gimprc' if (Test-Path $rc) { $c = Get-Content $rc -Raw; if ($c -notmatch 'show-welcome-dialog') { Add-Content $rc "`n(show-welcome-dialog no)" } } else { Set-Content $rc '(show-welcome-dialog no)' } } function Close-Explorer { Get-Process explorer -ErrorAction SilentlyContinue | Out-Null $shell = New-Object -ComObject Shell.Application foreach ($w in @($shell.Windows())) { try { $w.Quit() } catch { } } Start-Sleep 2 } function Snapshot-Config($tag) { $d = Join-Path $Cfg '3.2' $facts["config_$tag"] = [ordered]@{ appdata_gimp = @(Tree $Cfg); files_32 = @(Tree $d) toolrc = (Hash (Join-Path $d 'toolrc')); sessionrc = (Hash (Join-Path $d 'sessionrc')); shortcutsrc = (Hash (Join-Path $d 'shortcutsrc')) toolrc_has_vector = $(if (Test-Path (Join-Path $d 'toolrc')) { [bool](Select-String -Path (Join-Path $d 'toolrc') -Pattern 'gimp-vector-tool' -Quiet) } else { $null }) } if (Test-Path (Join-Path $d 'toolrc')) { Copy-Item (Join-Path $d 'toolrc') (Join-Path $out "$tag-toolrc.txt") -Force } } function Copy-Contents($from, $to) { New-Item -ItemType Directory -Force $to | Out-Null; Copy-Item -Path (Join-Path $from '*') -Destination $to -Recurse -Force } function Repair-32($d) { # Our repair for PhotoGIMP 3.1 on GIMP 3.2: the "vectors" names were renamed to "path" in GIMP 3.2. (Get-Content (Join-Path $d 'toolrc') -Raw) -replace '"gimp-vector-tool"', '"gimp-path-tool"' | Set-Content (Join-Path $d 'toolrc') -NoNewline (Get-Content (Join-Path $d 'sessionrc') -Raw) -replace '"gimp-vectors-list"', '"gimp-path-list"' | Set-Content (Join-Path $d 'sessionrc') -NoNewline (Get-Content (Join-Path $d 'shortcutsrc') -Raw) -replace '"tools-vector"', '"tools-path"' | Set-Content (Join-Path $d 'shortcutsrc') -NoNewline } function Reset-Stock { Remove-Item -Recurse -Force (Join-Path $Cfg '3.2') -ErrorAction SilentlyContinue Remove-Item -Recurse -Force (Join-Path $Cfg '3.0') -ErrorAction SilentlyContinue $p = Start-Gimp 'reset' -Settle 10; Stop-Gimp $p 'reset' Set-NoWelcome (Join-Path $Cfg '3.2') } try { Step 'download GIMP' $setup = Join-Path $env:TEMP 'gimp-3.2.6-setup.exe' Invoke-WebRequest -Uri $GimpUrl -OutFile $setup -UseBasicParsing $facts.gimp_setup_sha256 = Hash $setup; $facts.gimp_setup_sha_ok = ($facts.gimp_setup_sha256 -eq $GimpSha) Step 'install GIMP (per user, silent)' $t = Measure-Command { Start-Process $setup -ArgumentList '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', '/CURRENTUSER', '/SP-' -Wait } $facts.gimp_install_seconds = [int]$t.TotalSeconds $bin = Join-Path $env:LOCALAPPDATA 'Programs\GIMP 3\bin' $facts.gimp_bin = $bin; $facts.gimp_exes = @(Tree $bin | Where-Object { $_ -match '^gimp.*\.exe$' }) $script:GimpExe = Join-Path $bin 'gimp-3.2.exe' if (-not (Test-Path $script:GimpExe)) { $script:GimpExe = (Get-ChildItem $bin -Filter 'gimp-3*.exe' | Where-Object { $_.Name -notmatch 'console|debug|test' } | Select-Object -First 1).FullName } $facts.gimp_exe = $script:GimpExe $sm = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs' $facts.start_menu = @(Get-ChildItem $sm -Recurse -Filter '*GIMP*' -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName.Replace($sm, '').TrimStart('\') }) $facts.gimp_version = (& (Join-Path $bin 'gimp-console-3.2.exe') --version 2>&1 | Out-String).Trim() Step 'first run (creates the settings folder)' $facts.appdata_gimp_before = @(Tree $Cfg) $script:KeepWelcome = $true $p = Start-Gimp '01-first-run' -Settle 20 $script:KeepWelcome = $false Shot '01-first-run' $p.MainWindowHandle Stop-Gimp $p 'first-run' $facts.appdata_gimp_after_first_run = @(Tree $Cfg) $facts.localappdata_gimp = @(Tree (Join-Path $env:LOCALAPPDATA 'GIMP')) Snapshot-Config 'stock' Set-NoWelcome (Join-Path $Cfg '3.2') Step 'Win+R and Explorer at %APPDATA%\GIMP' Key @(0x5B, 0x52); Start-Sleep 2 [System.Windows.Forms.SendKeys]::SendWait('{%}APPDATA{%}\GIMP'); Start-Sleep 1 Shot '02-run-dialog' [System.Windows.Forms.SendKeys]::SendWait('{ENTER}'); Start-Sleep 4 Shot '03-explorer-appdata-gimp' Close-Explorer Step 'download PhotoGIMP 3.1' $zip = Join-Path $Desk 'PhotoGIMP.zip' Invoke-WebRequest -Uri $PgUrl -OutFile $zip -UseBasicParsing $facts.photogimp_zip_sha256 = Hash $zip; $facts.photogimp_zip_sha_ok = ($facts.photogimp_zip_sha256 -eq $PgSha) $ex = Join-Path $Desk 'PhotoGIMP' Expand-Archive $zip -DestinationPath $ex -Force $facts.zip_top = @(Tree $ex); $facts.zip_30 = @(Tree (Join-Path $ex '3.0')) Start-Process explorer.exe $ex; Start-Sleep 4 Shot '04-extracted-zip' Close-Explorer $src30 = Join-Path $ex '3.0' $site = Join-Path $root 'site' # Paste a path-finder command the way a user does: PowerShell reads it line by line from the console input. function Paste-Site($name) { $o = ((Get-Content (Join-Path $site $name) -Raw) + "`n`n") | & powershell -NoProfile -ExecutionPolicy Bypass -Command - 2>&1 | Out-String Set-Content (Join-Path $out "paste-$name.txt") $o; Log "paste $name exit $LASTEXITCODE"; return $o } Step 'M: PhotoGIMP 3.1 in a 3.0 folder before GIMP 3.2 first start (migration)' Remove-Item -Recurse -Force (Join-Path $Cfg '3.2'), (Join-Path $Cfg '3.0') -ErrorAction SilentlyContinue Copy-Item $src30 (Join-Path $Cfg '3.0') -Recurse -Force $script:KeepWelcome = $true $p = Start-Gimp '20-migrate-first' -WithImage $script:KeepWelcome = $false Snapshot-Config 'migrate_first'; Shot '20-migrate-first' $p.MainWindowHandle Stop-Gimp $p 'migrate'; Snapshot-Config 'migrate_after_quit' $p = Start-Gimp '21-migrate-second' -WithImage; Shot '21-migrate-second' $p.MainWindowHandle; Stop-Gimp $p 'migrate2'; Snapshot-Config 'migrate_after_second_quit' Step 'S: path finder install (pasted), two starts' Reset-Stock $facts.paste_install = Paste-Site 'win-install-release-32.ps1'; Snapshot-Config 'paste_installed' $p = Start-Gimp '22-paste-installed' -WithImage; Shot '22-paste-installed' $p.MainWindowHandle; Stop-Gimp $p 'paste'; Snapshot-Config 'paste_after_quit' $p = Start-Gimp '23-paste-second' -WithImage; Shot '23-paste-second' $p.MainWindowHandle; Stop-Gimp $p 'paste2'; Snapshot-Config 'paste_after_second_quit' Step 'R: refuses while GIMP runs' $before = @(Tree $Cfg) -join ',' $p = Start-Gimp '24-running' -Settle 10 $facts.paste_running = Paste-Site 'win-install-release-32.ps1' $facts.running_folders_unchanged = ((@(Tree $Cfg) -join ',') -eq $before) Stop-Gimp $p 'running' Step 'U: remove PhotoGIMP only, restore backup, master, repair, reset (pasted)' $facts.paste_uninstall_files = Paste-Site 'win-uninstall-files-32.ps1'; Snapshot-Config 'paste_uninstalled' $facts.paste_restore = Paste-Site 'win-uninstall-backup-32.ps1'; Snapshot-Config 'paste_restored' $facts.paste_master = Paste-Site 'win-install-master-32.ps1'; Snapshot-Config 'paste_master' $p = Start-Gimp '25-paste-master' -WithImage; Shot '25-paste-master' $p.MainWindowHandle; Stop-Gimp $p 'master'; Snapshot-Config 'paste_master_after_quit' Reset-Stock; Copy-Contents $src30 (Join-Path $Cfg '3.2') $facts.paste_repair = Paste-Site 'win-repair-32.ps1'; Snapshot-Config 'paste_repaired' $facts.paste_reset = Paste-Site 'win-uninstall-all-32.ps1'; $facts.appdata_after_reset = @(Tree $Cfg) if (Get-Command choco -ErrorAction SilentlyContinue) { Step 'C1: Chocolatey on a clean GIMP 3.2 profile' Get-ChildItem $Cfg -Directory | Remove-Item -Recurse -Force Reset-Stock $c = (choco install photogimp -y --no-progress 2>&1 | Out-String); Set-Content (Join-Path $out 'choco1-install.txt') $c $facts.choco1_after_install = @(Tree $Cfg); $facts.choco1_30 = @(Tree (Join-Path $Cfg '3.0')) $p = Start-Gimp '26-choco-clean' -WithImage; Shot '26-choco-clean' $p.MainWindowHandle; Stop-Gimp $p 'choco1'; Snapshot-Config 'choco1' $c = (choco uninstall photogimp -y --no-progress 2>&1 | Out-String); Set-Content (Join-Path $out 'choco1-uninstall.txt') $c $facts.choco1_after_uninstall = @(Tree $Cfg); $facts.choco1_30_after_uninstall = @(Tree (Join-Path $Cfg '3.0')) Step 'C2: Chocolatey before GIMP 3.2 first start (3.2 folder does not exist yet)' Get-ChildItem $Cfg -Directory | Remove-Item -Recurse -Force $c = (choco install photogimp -y --no-progress --force 2>&1 | Out-String); Set-Content (Join-Path $out 'choco2-install.txt') $c $facts.choco2_after_install = @(Tree $Cfg) $script:KeepWelcome = $true $p = Start-Gimp '27-choco-before-first-start' -WithImage $script:KeepWelcome = $false Snapshot-Config 'choco2'; Shot '27-choco-before-first-start' $p.MainWindowHandle; Stop-Gimp $p 'choco2'; Snapshot-Config 'choco2_after_quit' } else { $facts.choco = 'not available on this runner' } } catch { Log "FAILED: $_"; $facts.error = "$_" } finally { Get-Process -Name 'gimp*' -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Save 'facts.json' $facts Log 'done' }