ofacturare_editare.prg (nou): helpere comune - garda eFactura, cursoarele notei si ale rulajelor, randul de vanzare corespunzator notei si liniile de articole citite din view-ul VVANZARI_ARTICOLE. omodificari.vc2 (frm_modific2024): pagina noua de articole ale facturii, deocamdata doar afisare. Apare numai cand ofacturare_editare.prg e inregistrat si documentul are rand in VANZARI; in ROACONT si ROAGEST, unde fisierul nu e incarcat, pagina lipseste si registrul jurnal ramane neatins. Randul de vanzare al notei se cauta pe toate tripletele distincte (nract, serie_act, dataact) din nota, pentru ca primul rand poate fi o incasare. ofacturare_comun.vc2 (frm_facturi): do_editare_factura si butonul aferent, dupa modelul lui do_sterge, cu garzile de luna inchisa, luna curenta, document sters, referinte si eFactura. docs: comentariile se scriu strict necesar, si in cod si in scripturile de migrare - fara referinte la planuri, stories, decizii sau erori, istoricul doar in antetul fisierului. Plus regula zero (predarea contextului), capcanele de la testarea headless si completari pe fluxul text -> binar. utile/Teste: suita de regresie pentru editarea facturii si harness-ul watchdog (nu sunt in SVN, unde utile/Teste e ignorat). .gitignore ignora si capturile PNG si watchdog_out/, ramase din rulari. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
391 lines
18 KiB
PowerShell
391 lines
18 KiB
PowerShell
# watchdog_vfp.ps1 - lanseaza un test VFP headless si detecteaza dialoguri native blocante
|
|
# (mesaje Windows, "View Parameter", erori modale) care nu apar in logul scris de script si
|
|
# nu sunt prinse de ON ERROR / mock-uri - cazul clasic de proces "agatat" cu CPU~0.
|
|
#
|
|
# Ce face: sterge .fxp-ul vechi, lanseaza `vfp9.exe -A -T "<script>"`, apoi polleaza la interval
|
|
# scurt ferestrele TOP-LEVEL ale procesului (EnumWindows + GetWindowThreadProcessId). Fereastra
|
|
# principala VFP se identifica prin Process.MainWindowHandle (.NET) - NU dupa numele clasei:
|
|
# VFP inregistreaza clase diferite prefixate "vfp9..." atat pentru shell-ul principal cat si
|
|
# pentru dialogurile proprii (ex. "View Parameter"), deci clasificarea pe clasa e nesigura.
|
|
# Orice fereastra NOUA, diferita de MainWindowHandle, e tratata ca dialog blocant.
|
|
#
|
|
# Pentru fiecare dialog detectat: captura PNG (PrintWindow, fallback CopyFromScreen daca iese
|
|
# neagra) + dump text (titlu, clasa, si textul/clasa fiecarui control copil via WM_GETTEXT - se
|
|
# citesc controale native Windows chiar daca apartin unui proces strain pe alta arhitectura).
|
|
#
|
|
# INTERZIS: watchdog-ul NU are voie sa foloseasca input real de tastatura/mouse (keybd_event,
|
|
# SetForegroundWindow, SendInput, mouse_event) - masina e PARTAJATA cu utilizatorul, iar un
|
|
# ESCAPE/click injectat la nivel de sistem ajunge in orice fereastra are focus in acel moment,
|
|
# nu neaparat in dialogul tintit (verificat: a ajuns in IDE-ul VFP al utilizatorului aflat in
|
|
# lucru in paralel, intrerupandu-l). -AutoDismiss foloseste STRICT mesaje Windows tintite pe
|
|
# handle-ul dialogului: buton copil cu text Cancel/Anulare/Renunta -> BM_CLICK; altfel
|
|
# WM_COMMAND IDCANCEL -> ESCAPE ca mesaj (WM_KEYDOWN/WM_KEYUP, nu input real) -> WM_CLOSE, in
|
|
# ordine, primul care inchide fereastra castiga. Dialogurile proprii VFP owner-drawn (ex.
|
|
# "View Parameter" - butoanele sunt desenate, nu HWND-uri reale) pot sa NU raspunda la niciunul -
|
|
# atunci dismiss-ul ESUEAZA CINSTIT (ramane deschis pana la timeout), nu se cade pe input real.
|
|
# Captura (PNG+dump text) e intotdeauna de incredere, indiferent de rezultatul dismiss-ului.
|
|
# Continua polling-ul dupa fiecare dismiss reusit, ca sa prinda si eventuale dialoguri urmatoare
|
|
# (pana la -MaxDialogs). Fara -AutoDismiss: captureaza primul dialog gasit, apoi opreste
|
|
# polling-ul (nu-l atinge).
|
|
#
|
|
# Procesul e omorat mereu la iesire (finally) - nu ramane niciodata viu.
|
|
#
|
|
# Utilizare:
|
|
# powershell -ExecutionPolicy Bypass -File watchdog_vfp.ps1 -Script "D:\...\test_x.prg" -AutoDismiss
|
|
# powershell -ExecutionPolicy Bypass -File watchdog_vfp.ps1 -Script "D:\...\test_x.prg" -TimeoutSec 60 -OutDir "D:\...\out"
|
|
#
|
|
# Parametri:
|
|
# -Script cale completa la .prg de rulat (obligatoriu)
|
|
# -Vfp cale la vfp9.exe (implicit instalarea standard x86)
|
|
# -TimeoutSec timeout total de polling (implicit 90)
|
|
# -OutDir folder pentru PNG-uri + log text (implicit "<folderul scriptului>\watchdog_out")
|
|
# -AutoDismiss inchide automat dialogurile gasite (Cancel/WM_CLOSE) si continua polling-ul
|
|
# -MaxDialogs cate dialoguri succesive sa trateze cu -AutoDismiss (implicit 10)
|
|
#
|
|
# Iesire: rezumat pe stdout (numar dialoguri, textul fiecaruia, caile PNG, exit code-ul
|
|
# procesului) + acelasi continut scris in "<Script>_watchdog.log" din -OutDir.
|
|
|
|
param(
|
|
[Parameter(Mandatory=$true)][string]$Script,
|
|
[string]$Vfp = 'C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe',
|
|
[int]$TimeoutSec = 90,
|
|
[string]$OutDir,
|
|
[switch]$AutoDismiss,
|
|
[int]$MaxDialogs = 10
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
if (-not (Test-Path $Script)) { throw "Nu gasesc scriptul: $Script" }
|
|
$Script = (Resolve-Path $Script).Path
|
|
$ScriptDir = [System.IO.Path]::GetDirectoryName($Script)
|
|
$ScriptBase = [System.IO.Path]::GetFileNameWithoutExtension($Script)
|
|
if (-not $OutDir) { $OutDir = Join-Path $ScriptDir 'watchdog_out' }
|
|
if (-not (Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir | Out-Null }
|
|
$LogPath = Join-Path $OutDir ($ScriptBase + '_watchdog.log')
|
|
|
|
Add-Type -AssemblyName System.Drawing
|
|
|
|
Add-Type @"
|
|
using System;
|
|
using System.Text;
|
|
using System.Runtime.InteropServices;
|
|
|
|
public class WdWin32 {
|
|
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern bool IsWindowVisible(IntPtr hWnd);
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern bool IsWindow(IntPtr hWnd);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
|
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "SendMessage")]
|
|
public static extern IntPtr SendMessageText(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam);
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct RECT { public int Left, Top, Right, Bottom; }
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, uint nFlags);
|
|
}
|
|
"@
|
|
|
|
$WM_GETTEXT = 0x000D
|
|
$WM_CLOSE = 0x0010
|
|
$BM_CLICK = 0x00F5
|
|
|
|
function Get-TopWindowsForPid([int]$ProcId) {
|
|
$result = New-Object System.Collections.Generic.List[IntPtr]
|
|
$cb = {
|
|
param($hWnd, $lParam)
|
|
$owner = 0
|
|
[void][WdWin32]::GetWindowThreadProcessId($hWnd, [ref]$owner)
|
|
if ($owner -eq $ProcId -and [WdWin32]::IsWindowVisible($hWnd)) { $result.Add($hWnd) }
|
|
return $true
|
|
}
|
|
[WdWin32]::EnumWindows($cb, [IntPtr]::Zero) | Out-Null
|
|
return $result
|
|
}
|
|
|
|
function Get-ChildWindowsOf([IntPtr]$hWndParent) {
|
|
$result = New-Object System.Collections.Generic.List[IntPtr]
|
|
$cb = {
|
|
param($hWnd, $lParam)
|
|
$result.Add($hWnd)
|
|
return $true
|
|
}
|
|
[WdWin32]::EnumChildWindows($hWndParent, $cb, [IntPtr]::Zero) | Out-Null
|
|
return $result
|
|
}
|
|
|
|
# WM_GETTEXT (nu GetWindowText) - citeste text de la controale ale unui proces strain, indiferent
|
|
# de arhitectura (32/64 bit); GetWindowText poate esua tacut pe controale custom VFP.
|
|
function Get-WmText([IntPtr]$hWnd, [int]$MaxLen = 1024) {
|
|
$sb = New-Object System.Text.StringBuilder $MaxLen
|
|
[WdWin32]::SendMessageText($hWnd, $WM_GETTEXT, $MaxLen, $sb) | Out-Null
|
|
return $sb.ToString()
|
|
}
|
|
|
|
function Get-WinClassName([IntPtr]$hWnd) {
|
|
$sb = New-Object System.Text.StringBuilder 256
|
|
[WdWin32]::GetClassName($hWnd, $sb, $sb.Capacity) | Out-Null
|
|
return $sb.ToString()
|
|
}
|
|
|
|
function Test-BitmapAllBlack([System.Drawing.Bitmap]$bmp) {
|
|
$w = $bmp.Width; $h = $bmp.Height
|
|
if ($w -le 0 -or $h -le 0) { return $true }
|
|
for ($i = 0; $i -lt 9; $i++) {
|
|
$x = [Math]::Min($w - 1, [int]($w * (($i % 3) + 0.5) / 3))
|
|
$y = [Math]::Min($h - 1, [int]($h * ([Math]::Floor($i / 3) + 0.5) / 3))
|
|
$px = $bmp.GetPixel($x, $y)
|
|
if ($px.R -ne 0 -or $px.G -ne 0 -or $px.B -ne 0) { return $false }
|
|
}
|
|
return $true
|
|
}
|
|
|
|
# PrintWindow direct pe handle; fallback CopyFromScreen daca iese neagra (fereastra themed/GDI+
|
|
# care nu se randeaza corect prin PrintWindow). Foloseste doar pentru dialoguri surprinse
|
|
# on-screen - nu se muta off-screen aici (spre deosebire de vfp_ui_harness.ps1), fiindca scopul
|
|
# e sa surprindem exact dialogul care blocheaza, o singura data.
|
|
function Save-DialogScreenshot([IntPtr]$hWnd, [string]$Path) {
|
|
$rect = New-Object WdWin32+RECT
|
|
[WdWin32]::GetWindowRect($hWnd, [ref]$rect) | Out-Null
|
|
$w = $rect.Right - $rect.Left
|
|
$h = $rect.Bottom - $rect.Top
|
|
if ($w -le 0 -or $h -le 0) { return $false }
|
|
$bmp = New-Object System.Drawing.Bitmap $w, $h
|
|
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
$hdc = $g.GetHdc()
|
|
$ok = [WdWin32]::PrintWindow($hWnd, $hdc, 2)
|
|
$g.ReleaseHdc($hdc)
|
|
$g.Dispose()
|
|
if ((-not $ok) -or (Test-BitmapAllBlack $bmp)) {
|
|
$bmp.Dispose()
|
|
$bmp = New-Object System.Drawing.Bitmap $w, $h
|
|
$g2 = [System.Drawing.Graphics]::FromImage($bmp)
|
|
try { $g2.CopyFromScreen($rect.Left, $rect.Top, 0, 0, (New-Object System.Drawing.Size $w, $h)) } catch {}
|
|
$g2.Dispose()
|
|
}
|
|
$bmp.Save($Path, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
$bmp.Dispose()
|
|
return $true
|
|
}
|
|
|
|
function Get-DialogDump([IntPtr]$hWnd, [System.Collections.Generic.List[IntPtr]]$children) {
|
|
$title = Get-WmText $hWnd
|
|
$class = Get-WinClassName $hWnd
|
|
$lines = New-Object System.Collections.Generic.List[string]
|
|
$lines.Add("dialog hwnd=$hWnd class='$class' title='$title'")
|
|
foreach ($c in $children) {
|
|
$ct = (Get-WmText $c 512).Trim()
|
|
$cc = Get-WinClassName $c
|
|
if ($ct -or $cc) { $lines.Add(" child hwnd=$c class='$cc' text='$ct'") }
|
|
}
|
|
# unary comma obligatoriu: fara ea, un List[string] cu UN SINGUR element (dialog fara copii,
|
|
# ex. "View Parameter") e "descompus" de PowerShell la scalar string simplu la return, iar
|
|
# indexarea ulterioara $dumpLines[0] citeste primul CARACTER, nu primul rand - prins la testul
|
|
# pe test_page3_articole.prg (rezumatul arata titlul "d" in loc de linia intreaga)
|
|
return , $lines.ToArray()
|
|
}
|
|
|
|
function Find-DismissButton([System.Collections.Generic.List[IntPtr]]$children) {
|
|
foreach ($c in $children) {
|
|
if ((Get-WinClassName $c) -ne 'Button') { continue }
|
|
$txt = ((Get-WmText $c 256) -replace '&', '').Trim()
|
|
if ($txt -ieq 'Cancel' -or $txt -ieq 'Anulare' -or $txt -ieq 'Renunta' -or $txt -ieq 'Renunță') { return $c }
|
|
}
|
|
return [IntPtr]::Zero
|
|
}
|
|
|
|
$WM_COMMAND = 0x0111
|
|
$WM_KEYDOWN = 0x0100
|
|
$WM_KEYUP = 0x0101
|
|
$VK_ESCAPE = 0x1B
|
|
$IDCANCEL = 2
|
|
|
|
# INTERZIS: input real de tastatura/mouse (keybd_event, SetForegroundWindow, SendInput,
|
|
# mouse_event). Masina e PARTAJATA cu utilizatorul - un ESCAPE injectat la nivel de sistem
|
|
# ajunge in orice fereastra are focus in acel moment, INDIFERENT carui proces ii apartine
|
|
# (verificat: a ajuns in IDE-ul VFP al utilizatorului, aflat in lucru in paralel, intrerupandu-l
|
|
# cu o eroare + "Execution was canceled by the user"). Foloseste STRICT mesaje Windows tintite pe
|
|
# HANDLE-ul dialogului (PostMessage/SendMessage: WM_COMMAND IDCANCEL, WM_KEYDOWN/WM_KEYUP
|
|
# VK_ESCAPE, WM_CLOSE, BM_CLICK) - astea nu ating focusul/inputul global, raman izolate la
|
|
# fereastra tintita. Dialogurile native VFP proprii (ex. "View Parameter") NU au controale copil
|
|
# reale (owner-drawn) si pot sa NU raspunda la niciunul dintre mesajele de mai jos - in acel caz
|
|
# dismiss-ul ESUEAZA CINSTIT (dialogul ramane deschis, procesul e omorat la timeout de finally),
|
|
# nu se cade pe input real. Captura (PNG+dump text) ramane oricum de incredere, indiferent de
|
|
# rezultatul dismiss-ului.
|
|
function Dismiss-Dialog([IntPtr]$hWnd, [System.Collections.Generic.List[IntPtr]]$children) {
|
|
$btn = Find-DismissButton $children
|
|
if ($btn -ne [IntPtr]::Zero) {
|
|
[WdWin32]::SendMessage($btn, $BM_CLICK, [IntPtr]::Zero, [IntPtr]::Zero) | Out-Null
|
|
Start-Sleep -Milliseconds 300
|
|
if (-not [WdWin32]::IsWindow($hWnd)) { return 'click Cancel/Anulare' }
|
|
return 'NEREUSIT: BM_CLICK pe Cancel/Anulare nu a inchis dialogul'
|
|
}
|
|
|
|
[WdWin32]::SendMessage($hWnd, $WM_COMMAND, [IntPtr]$IDCANCEL, [IntPtr]::Zero) | Out-Null
|
|
Start-Sleep -Milliseconds 300
|
|
if (-not [WdWin32]::IsWindow($hWnd)) { return 'WM_COMMAND IDCANCEL' }
|
|
|
|
[WdWin32]::SendMessage($hWnd, $WM_KEYDOWN, [IntPtr]$VK_ESCAPE, [IntPtr]::Zero) | Out-Null
|
|
[WdWin32]::SendMessage($hWnd, $WM_KEYUP, [IntPtr]$VK_ESCAPE, [IntPtr]::Zero) | Out-Null
|
|
Start-Sleep -Milliseconds 300
|
|
if (-not [WdWin32]::IsWindow($hWnd)) { return 'tasta ESCAPE trimisa cu SendMessage (mesaj, nu input real)' }
|
|
|
|
[WdWin32]::SendMessage($hWnd, $WM_CLOSE, [IntPtr]::Zero, [IntPtr]::Zero) | Out-Null
|
|
Start-Sleep -Milliseconds 300
|
|
if (-not [WdWin32]::IsWindow($hWnd)) { return 'WM_CLOSE' }
|
|
|
|
$motiv = if ($children.Count -eq 0) { 'fara controale copil (owner-drawn) si niciun mesaj tintit nu a functionat' } else { 'niciun mesaj tintit (WM_COMMAND/ESCAPE mesaj/WM_CLOSE) nu a functionat' }
|
|
return "NEREUSIT: dialogul a ramas deschis ($motiv) - fara input real, per regula masinii partajate"
|
|
}
|
|
|
|
# omoara doar vfp9 lansate pe ACEST script (linia de comanda contine calea lui), nu instante straine
|
|
function Stop-VfpForScript([string]$ScriptPath) {
|
|
Get-CimInstance Win32_Process -Filter "Name='vfp9.exe'" -ErrorAction SilentlyContinue | ForEach-Object {
|
|
if ($_.CommandLine -and $_.CommandLine.ToLower().Contains($ScriptPath.ToLower())) {
|
|
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- pregatire ---
|
|
$Fxp = [System.IO.Path]::ChangeExtension($Script, 'fxp')
|
|
if (Test-Path $Fxp) { Remove-Item $Fxp -Force }
|
|
Stop-VfpForScript $Script
|
|
Start-Sleep -Milliseconds 300
|
|
|
|
$log = New-Object System.Collections.Generic.List[string]
|
|
function Log([string]$s) { Write-Host $s; $log.Add($s) }
|
|
|
|
Log "watchdog_vfp: lansez $Vfp -A -T `"$Script`""
|
|
$proc = Start-Process -FilePath $Vfp -ArgumentList @('-A', '-T', "`"$Script`"") -PassThru
|
|
|
|
$dialogs = New-Object System.Collections.Generic.List[object]
|
|
$exitCode = $null
|
|
|
|
try {
|
|
# Fereastra principala = Process.MainWindowHandle (.NET), latch-uita o singura data. NU se
|
|
# poate distinge dupa numele clasei: VFP inregistreaza clase diferite prefixate "vfp9..." atat
|
|
# pentru shell-ul principal ("vfp99400000") CAT SI pentru dialoguri native proprii precum
|
|
# "View Parameter" ("vfp994000002") - verificat empiric, clasificarea pe clasa a lasat
|
|
# "View Parameter" nedetectat. MainWindowHandle e acelasi mecanism folosit deja cu succes in
|
|
# vfp_ui_harness.ps1 (Wait-MainWindowHandle) pentru shell-ul VFP.
|
|
$mainHwnd = [IntPtr]::Zero
|
|
$mwDeadline = (Get-Date).AddSeconds(20)
|
|
while ($mainHwnd -eq [IntPtr]::Zero -and (Get-Date) -lt $mwDeadline) {
|
|
$proc.Refresh()
|
|
if ($proc.HasExited) { break }
|
|
if ($proc.MainWindowHandle -ne [IntPtr]::Zero) { $mainHwnd = $proc.MainWindowHandle }
|
|
else { Start-Sleep -Milliseconds 200 }
|
|
}
|
|
if ($mainHwnd -ne [IntPtr]::Zero) {
|
|
Log ("fereastra principala VFP: hwnd={0} class='{1}' title='{2}'" -f $mainHwnd, (Get-WinClassName $mainHwnd), (Get-WmText $mainHwnd))
|
|
} else {
|
|
Log "ATENTIE: MainWindowHandle nu a aparut in 20s - orice fereastra gasita va fi tratata ca dialog."
|
|
}
|
|
|
|
$knownWindows = New-Object System.Collections.Generic.HashSet[IntPtr]
|
|
if ($mainHwnd -ne [IntPtr]::Zero) { [void]$knownWindows.Add($mainHwnd) }
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
|
$stop = $false
|
|
|
|
while (-not $stop -and (Get-Date) -lt $deadline) {
|
|
$proc.Refresh()
|
|
if ($proc.HasExited) { Log "proces terminat singur (exit code $($proc.ExitCode))"; break }
|
|
|
|
# daca MainWindowHandle inca nu era prins, incearca din nou (fereastra principala poate
|
|
# aparea mai tarziu decat asteptarea initiala de 20s in cazuri lente)
|
|
if ($mainHwnd -eq [IntPtr]::Zero -and $proc.MainWindowHandle -ne [IntPtr]::Zero) {
|
|
$mainHwnd = $proc.MainWindowHandle
|
|
[void]$knownWindows.Add($mainHwnd)
|
|
Log ("fereastra principala VFP (intarziata): hwnd={0} class='{1}' title='{2}'" -f $mainHwnd, (Get-WinClassName $mainHwnd), (Get-WmText $mainHwnd))
|
|
}
|
|
|
|
$windows = Get-TopWindowsForPid $proc.Id
|
|
|
|
foreach ($w in $windows) {
|
|
if ($knownWindows.Contains($w)) { continue }
|
|
[void]$knownWindows.Add($w)
|
|
|
|
if ($w -eq $mainHwnd) { continue }
|
|
|
|
$children = Get-ChildWindowsOf $w
|
|
$dumpLines = Get-DialogDump $w $children
|
|
$idx = $dialogs.Count
|
|
$png = Join-Path $OutDir ("{0}_dialog{1}.png" -f $ScriptBase, $idx)
|
|
Save-DialogScreenshot $w $png | Out-Null
|
|
|
|
Log "=== DIALOG $idx detectat ==="
|
|
foreach ($l in $dumpLines) { Log $l }
|
|
Log " screenshot: $png"
|
|
|
|
$dismissMethod = $null
|
|
if ($AutoDismiss) {
|
|
$dismissMethod = Dismiss-Dialog $w $children
|
|
Log " dismiss: $dismissMethod"
|
|
}
|
|
|
|
$dialogs.Add([pscustomobject]@{
|
|
Index = $idx
|
|
Title = ($dumpLines[0])
|
|
Png = $png
|
|
Dismiss = $dismissMethod
|
|
DumpText = ($dumpLines -join "`r`n")
|
|
})
|
|
|
|
if ($dialogs.Count -ge $MaxDialogs) { Log "MaxDialogs ($MaxDialogs) atins."; $stop = $true; break }
|
|
if (-not $AutoDismiss) { Log "AutoDismiss oprit - opresc polling-ul dupa primul dialog."; $stop = $true; break }
|
|
}
|
|
|
|
if (-not $stop) { Start-Sleep -Milliseconds 700 }
|
|
}
|
|
|
|
if (-not $proc.HasExited -and (Get-Date) -ge $deadline) {
|
|
Log "TIMEOUT (${TimeoutSec}s) - procesul e inca viu, il opresc."
|
|
}
|
|
|
|
$proc.Refresh()
|
|
if ($proc.HasExited) { $exitCode = $proc.ExitCode }
|
|
}
|
|
finally {
|
|
if ($proc -and -not $proc.HasExited) {
|
|
try { $proc.Kill() } catch {}
|
|
}
|
|
Stop-VfpForScript $Script
|
|
}
|
|
|
|
# --- rezumat ---
|
|
Log ""
|
|
Log "=== REZUMAT ==="
|
|
Log ("dialoguri detectate: {0}" -f $dialogs.Count)
|
|
foreach ($d in $dialogs) {
|
|
Log (" [{0}] {1}" -f $d.Index, $d.Title)
|
|
Log (" png: {0}" -f $d.Png)
|
|
if ($d.Dismiss) { Log (" dismiss: {0}" -f $d.Dismiss) }
|
|
}
|
|
Log ("exit code proces: {0}" -f ($(if ($null -ne $exitCode) { $exitCode } else { '(omorat de watchdog, nu s-a terminat singur)' })))
|
|
Log ("log complet: {0}" -f $LogPath)
|
|
|
|
[System.IO.File]::WriteAllLines($LogPath, $log)
|