<# .SYNOPSIS Monitorizeaza UPS-ul serverului Oracle de productie (10.0.20.36 / ROA-CARAPETRU2) prin canalul HID nativ Windows si opreste curat baza inainte de epuizarea bateriei. .DESCRIPTION Inlocuieste ViewPower, care nu reusea handshake-ul de protocol cu acest UPS (5777 x "QPI return(NAK", zero citiri reusite) si a fost dezactivat pe 10.08.2026. Sursa de date este API-ul Windows GetSystemPowerStatus - exact acelasi semnal pe care il foloseste si Windows pentru actiunea proprie de "critical battery", deci daca merge oprirea automata a Windows-ului merge si scriptul asta. Ce face: - alerteaza pe email la trecerea pe baterie si la revenirea curentului; - alerteaza cand UPS-ul nu mai comunica deloc cu calculatorul (cazul care a trecut neobservat 17 zile in august 2026); - opreste curat Oracle (listener, apoi "shutdown immediate") si abia apoi Windows-ul, inainte ca bateria sa ajunga la pragul brutal al Windows-ului. Documentatie: docs/ups-server36-monitorizare.md .PARAMETER ConfigPath Calea catre config.json. Implicit, langa script. .PARAMETER Mode Run - bucla continua (modul in care ruleaza taskul programat). Once - o singura evaluare, apoi iese. Pentru depanare. Status - afiseaza starea curenta (UPS, Oracle, task, ultimele evenimente). TestMail - trimite un email de test si iese. TestShutdown - parcurge secventa de oprire in gol (dry-run), fara sa opreasca nimic. .NOTES Fisierul e intentionat fara diacritice: PowerShell 5.1 citeste .ps1 fara BOM ca ANSI, iar diacriticele ar ajunge corupte in emailuri. #> [CmdletBinding()] param( # Implicit, config.json de langa script. Nu se poate folosi $PSScriptRoot direct # in valoarea implicita: la legarea parametrilor e inca gol. [string]$ConfigPath, [ValidateSet('Run', 'Once', 'Status', 'TestMail', 'TestShutdown')] [string]$Mode = 'Run' ) Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 if (-not $ConfigPath) { $scriptDir = $PSScriptRoot if (-not $scriptDir) { $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path } $ConfigPath = Join-Path $scriptDir 'config.json' } # --------------------------------------------------------------------------- # API-ul de alimentare al Windows-ului # --------------------------------------------------------------------------- if (-not ('RomfastPowerStatus' -as [type])) { Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_POWER_STATUS { public byte ACLineStatus; public byte BatteryFlag; public byte BatteryLifePercent; public byte SystemStatusFlag; public int BatteryLifeTime; public int BatteryFullLifeTime; } public static class RomfastPowerStatus { [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetSystemPowerStatus(out SYSTEM_POWER_STATUS status); } '@ } # ACLineStatus: 0 = pe baterie, 1 = pe retea, 255 = necunoscut # BatteryFlag: 1 = high, 2 = low, 4 = critical, 8 = charging, # 128 = NU EXISTA BATERIE (UPS-ul nu mai comunica), 255 = necunoscut # BatteryLifePercent: 0..100, 255 = necunoscut # BatteryLifeTime: secunde ramase, -1 = necunoscut function Get-PowerReading { $st = New-Object SYSTEM_POWER_STATUS $ok = [RomfastPowerStatus]::GetSystemPowerStatus([ref]$st) if (-not $ok) { throw "GetSystemPowerStatus a esuat (Win32 error $([Runtime.InteropServices.Marshal]::GetLastWin32Error()))" } $noBattery = ($st.BatteryFlag -eq 128) -or ($st.BatteryFlag -eq 255) $percent = $null if ($st.BatteryLifePercent -le 100) { $percent = [int]$st.BatteryLifePercent } $runtime = $null if ($st.BatteryLifeTime -ge 0) { $runtime = [int]$st.BatteryLifeTime } [pscustomobject]@{ UpsPresent = (-not $noBattery) OnBattery = ($st.ACLineStatus -eq 0) AcKnown = ($st.ACLineStatus -eq 0 -or $st.ACLineStatus -eq 1) Percent = $percent RuntimeSec = $runtime ACLineStatus = [int]$st.ACLineStatus BatteryFlag = [int]$st.BatteryFlag } } # --------------------------------------------------------------------------- # Simulare - testarea ramurii "pe baterie" fara sa scoti UPS-ul din priza # --------------------------------------------------------------------------- # # Cat timp exista SIMULARE.json langa config.json, citirea reala e suprascrisa # cu valorile din el. Doua garzi, ca un fisier uitat sa nu opreasca serverul pe # date inventate: # - "expira" e OBLIGATORIU; fara el fisierul e ignorat, iar la depasire e sters; # - oprirea e in gol (dry-run) daca nu se cere explicit "dryRun": false. function Get-Simulation { param([string]$Path) if (-not (Test-Path -LiteralPath $Path)) { return $null } try { $s = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json } catch { Write-Log "SIMULARE.json nu se poate citi, il ignor: $($_.Exception.Message)" -Level WARN return $null } if (-not ($s.PSObject.Properties.Name -contains 'expira') -or -not $s.expira) { Write-Log 'SIMULARE.json nu are camp "expira" - ignorat, ca sa nu ramana activ la nesfarsit' -Level WARN return $null } if ((Get-Date) -gt [datetime]$s.expira) { Write-Log 'SIMULARE.json a expirat - il sterg si revin la citirea reala' -Level WARN Remove-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue return $null } return $s } function Merge-Simulation { param([object]$Reading, [object]$Sim) $o = $Reading.PSObject.Copy() $map = @{ onBattery = 'OnBattery' percent = 'Percent' runtimeSec = 'RuntimeSec' upsPresent = 'UpsPresent' } foreach ($k in $map.Keys) { if ($Sim.PSObject.Properties.Name -contains $k) { $o.($map[$k]) = $Sim.$k } } $o.AcKnown = $true return $o } function Get-UpsDeviceInfo { # Confirmare a doua, din WMI - folosita doar in alerte si in modul Status, # ca sa nu interogam WMI la fiecare ciclu. try { $b = Get-CimInstance Win32_Battery -ErrorAction Stop | Select-Object -First 1 if ($null -eq $b) { return 'Win32_Battery: nicio instanta (UPS-ul nu e vazut de Windows)' } return "Win32_Battery: $($b.Name), Status=$($b.Status), BatteryStatus=$($b.BatteryStatus), Incarcare=$($b.EstimatedChargeRemaining)%" } catch { return "Win32_Battery: interogare esuata ($($_.Exception.Message))" } } # --------------------------------------------------------------------------- # Configuratie, log, stare # --------------------------------------------------------------------------- function Get-Config { param([string]$Path) if (-not (Test-Path -LiteralPath $Path)) { throw "Lipseste fisierul de configurare: $Path" } $cfg = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json # Valori implicite pentru cheile care pot lipsi. $defaults = @{ enabled = $true pollSeconds = 15 shutdownAfterMinutesOnBattery = 10 shutdownAtBatteryPercent = 35 shutdownAtRuntimeSeconds = 0 # 0 = criteriu dezactivat confirmPolls = 3 commsLostPolls = 8 commsAlertRepeatHours = 24 } foreach ($k in $defaults.Keys) { if (-not ($cfg.PSObject.Properties.Name -contains $k)) { $cfg | Add-Member -NotePropertyName $k -NotePropertyValue $defaults[$k] } } return $cfg } $script:LogDir = $null function Write-Log { param( [string]$Message, [ValidateSet('INFO', 'WARN', 'ALERT', 'ERROR')] [string]$Level = 'INFO' ) $line = '{0} {1,-5} {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message Write-Host $line if ($script:LogDir) { try { if (-not (Test-Path -LiteralPath $script:LogDir)) { New-Item -ItemType Directory -Path $script:LogDir -Force | Out-Null } $file = Join-Path $script:LogDir ('ups-monitor-{0}.log' -f (Get-Date -Format 'yyyy-MM')) Add-Content -LiteralPath $file -Value $line -Encoding UTF8 } catch { # Logul nu are voie sa opreasca monitorizarea. } } } function Get-State { param([string]$Path) if (Test-Path -LiteralPath $Path) { try { $loaded = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json return (Initialize-State -State $loaded) } catch { Write-Log "Fisierul de stare e corupt, se reia de la zero: $($_.Exception.Message)" -Level WARN } } return New-State } function New-State { [pscustomobject]@{ powerState = 'unknown' # online | battery | unknown onBatterySince = $null commsState = 'unknown' # ok | lost | unknown lastCommsAlert = $null shutdownTriggered = $false } } function Initialize-State { # Un state.json scris de o versiune mai veche poate sa nu aiba toate cheile, # iar Set-StrictMode ar pica pe prima proprietate lipsa. param([object]$State) $template = New-State foreach ($p in $template.PSObject.Properties) { if (-not ($State.PSObject.Properties.Name -contains $p.Name)) { $State | Add-Member -NotePropertyName $p.Name -NotePropertyValue $p.Value } } return $State } function Save-State { param([object]$State, [string]$Path) try { $dir = Split-Path -Parent $Path if ($dir -and -not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } $State | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $Path -Encoding UTF8 } catch { Write-Log "Nu am putut salva starea in $Path : $($_.Exception.Message)" -Level WARN } } # --------------------------------------------------------------------------- # Email # --------------------------------------------------------------------------- function Send-UpsMail { param( [object]$Config, [string]$Subject, [string]$Body ) # Un email esuat nu are voie sa opreasca bucla si nici secventa de oprire: # in pana de curent e foarte posibil sa fie cazut si switch-ul. try { $smtp = New-Object System.Net.Mail.SmtpClient($Config.smtp.server, [int]$Config.smtp.port) $smtp.EnableSsl = $true $smtp.Timeout = 20000 # UseDefaultCredentials trebuie pus inainte de Credentials: setarea lui # sterge acreditarile deja atribuite. $smtp.UseDefaultCredentials = $false $smtp.Credentials = New-Object System.Net.NetworkCredential($Config.smtp.user, $Config.smtp.password) $msg = New-Object System.Net.Mail.MailMessage $msg.From = New-Object System.Net.Mail.MailAddress($Config.smtp.from, "UPS $env:COMPUTERNAME") foreach ($to in $Config.mailTo) { $msg.To.Add($to) } $msg.Subject = $Subject $msg.Body = $Body $msg.SubjectEncoding = [Text.Encoding]::UTF8 $msg.BodyEncoding = [Text.Encoding]::UTF8 $smtp.Send($msg) $msg.Dispose() $smtp.Dispose() Write-Log "Email trimis: $Subject" return $true } catch { Write-Log "Email NETRIMIS ($Subject): $($_.Exception.Message)" -Level ERROR return $false } } function Format-Reading { param([object]$Reading, [object]$Config) $pct = 'necunoscut' if ($null -ne $Reading.Percent) { $pct = "$($Reading.Percent)%" } $rt = 'neraportat de UPS' if ($null -ne $Reading.RuntimeSec) { $rt = '{0} min' -f [math]::Round($Reading.RuntimeSec / 60, 1) } $alim = 'necunoscuta' if ($Reading.OnBattery) { $alim = 'PE BATERIE' } elseif ($Reading.AcKnown) { $alim = 'pe retea' } $vazut = 'NU' if ($Reading.UpsPresent) { $vazut = 'da' } $oracle = 'nedeterminat' try { $svc = Get-Service -Name $Config.oracle.service -ErrorAction Stop $lsn = Get-Service -Name $Config.oracle.listenerService -ErrorAction Stop $oracle = "$($svc.Name)=$($svc.Status), $($lsn.Name)=$($lsn.Status)" } catch { $oracle = "nedeterminat ($($_.Exception.Message))" } @" Server : $env:COMPUTERNAME (10.0.20.36) Moment : $(Get-Date -Format 'dd.MM.yyyy HH:mm:ss') Alimentare : $alim Incarcare : $pct Autonomie estim. : $rt UPS vazut de OS : $vazut $(Get-UpsDeviceInfo) Oracle : $oracle Praguri configurate: oprire dupa $($Config.shutdownAfterMinutesOnBattery) min pe baterie oprire sub $($Config.shutdownAtBatteryPercent)% incarcare Windows opreste singur, ca ultima plasa, la 20% "@ } # --------------------------------------------------------------------------- # Oprirea curata # --------------------------------------------------------------------------- function Stop-OracleCleanly { param([object]$Config, [switch]$DryRun) $oraHome = $Config.oracle.home $sid = $Config.oracle.sid $timeoutMs = [int]$Config.oracle.shutdownTimeoutSeconds * 1000 Write-Log "Opresc listenerul $($Config.oracle.listenerService)" if (-not $DryRun) { try { Stop-Service -Name $Config.oracle.listenerService -Force -ErrorAction Stop Write-Log 'Listener oprit' } catch { Write-Log "Listenerul nu s-a oprit: $($_.Exception.Message)" -Level WARN } } $sqlplus = Join-Path $oraHome 'bin\sqlplus.exe' if (-not (Test-Path -LiteralPath $sqlplus)) { Write-Log "sqlplus.exe lipseste la $sqlplus - trec direct pe oprirea serviciului" -Level WARN } else { Write-Log "Oracle: shutdown immediate pe instanta $sid (timeout $($Config.oracle.shutdownTimeoutSeconds)s)" if (-not $DryRun) { $tmpSql = Join-Path $env:TEMP ('ups-shutdown-{0}.sql' -f $PID) $tmpOut = Join-Path $env:TEMP ('ups-shutdown-{0}.out' -f $PID) try { Set-Content -LiteralPath $tmpSql -Encoding ASCII -Value @( 'whenever sqlerror exit failure' 'shutdown immediate' 'exit' ) $env:ORACLE_HOME = $oraHome $env:ORACLE_SID = $sid $p = Start-Process -FilePath $sqlplus ` -ArgumentList @('-S', '-L', '/ as sysdba', "@$tmpSql") ` -NoNewWindow -PassThru ` -RedirectStandardOutput $tmpOut -RedirectStandardError "$tmpOut.err" if ($p.WaitForExit($timeoutMs)) { $out = if (Test-Path $tmpOut) { (Get-Content -LiteralPath $tmpOut -Raw).Trim() } else { '' } Write-Log "sqlplus a iesit cu codul $($p.ExitCode). Iesire: $out" } else { Write-Log 'sqlplus nu a terminat in timpul alocat - il opresc si merg pe serviciu' -Level WARN try { $p.Kill() } catch { } } } catch { Write-Log "shutdown immediate a esuat: $($_.Exception.Message)" -Level WARN } finally { Remove-Item -LiteralPath $tmpSql, $tmpOut, "$tmpOut.err" -ErrorAction SilentlyContinue } } } # Plasa de siguranta: serviciul e configurat el insusi cu ORA_ROA_SHUTDOWNTYPE=immediate # si ORA_ROA_SHUTDOWN_TIMEOUT=90, deci oprirea lui e tot curata. Write-Log "Opresc serviciul $($Config.oracle.service)" if (-not $DryRun) { try { Stop-Service -Name $Config.oracle.service -Force -ErrorAction Stop Write-Log 'Serviciul Oracle oprit' } catch { Write-Log "Serviciul Oracle nu s-a oprit: $($_.Exception.Message)" -Level ERROR } } } function Invoke-UpsShutdown { param( [object]$Config, [object]$Reading, [string]$Reason, [switch]$DryRun ) $tag = if ($DryRun) { '[DRY-RUN] ' } else { '' } Write-Log "${tag}DECLANSEZ OPRIREA: $Reason" -Level ALERT # Emailul pleaca inainte de orice - in pana de curent reteaua poate cadea oricand. Send-UpsMail -Config $Config ` -Subject "$tag[UPS] OPRESC serverul $env:COMPUTERNAME - $Reason" ` -Body ("Motiv: $Reason`n`n" + (Format-Reading -Reading $Reading -Config $Config) + @" Secventa care urmeaza: 1. oprire listener Oracle 2. shutdown immediate pe instanta $($Config.oracle.sid) 3. oprire serviciu Oracle 4. shutdown Windows La revenirea curentului serverul NU porneste singur decat daca e activat "Restore on AC power loss" in BIOS. "@) | Out-Null Stop-OracleCleanly -Config $Config -DryRun:$DryRun Write-Log "${tag}Opresc Windows-ul" if (-not $DryRun) { # Out-Null: altfel iesirea lui shutdown.exe s-ar amesteca in valoarea # returnata de Invoke-Cycle, care e obiectul de stare. & shutdown.exe /s /f /t 30 /c "UPS: $Reason" | Out-Null } } # --------------------------------------------------------------------------- # Un ciclu de evaluare # --------------------------------------------------------------------------- function Invoke-Cycle { param([object]$Config, [object]$State, [switch]$DryRun) $now = Get-Date $r = Get-PowerReading # --- simulare (daca e activa, suprascrie citirea reala) ----------------- $sim = Get-Simulation -Path $script:SimPath if ($sim) { $r = Merge-Simulation -Reading $r -Sim $sim $simDryRun = $true if ($sim.PSObject.Properties.Name -contains 'dryRun') { $simDryRun = [bool]$sim.dryRun } if ($simDryRun) { $DryRun = $true } Write-Log ("SIMULARE ACTIVA pana la {0} (dryRun={1}): onBattery={2} percent={3} upsPresent={4}" -f ` $sim.expira, $simDryRun, $r.OnBattery, $r.Percent, $r.UpsPresent) -Level WARN } # --- comunicatia cu UPS-ul --------------------------------------------- if (-not $r.UpsPresent) { $script:CommsMissCount++ if ($script:CommsMissCount -ge $Config.commsLostPolls) { $repeat = $false if ($State.lastCommsAlert) { $repeat = ((New-TimeSpan -Start ([datetime]$State.lastCommsAlert) -End $now).TotalHours -ge $Config.commsAlertRepeatHours) } if ($State.commsState -ne 'lost' -or $repeat) { Write-Log 'UPS-ul nu mai este vazut de Windows' -Level ALERT Send-UpsMail -Config $Config ` -Subject "[UPS] $env:COMPUTERNAME nu mai comunica cu UPS-ul" ` -Body ((Format-Reading -Reading $r -Config $Config) + @" Windows nu mai vede bateria UPS-ului (BatteryFlag=$($r.BatteryFlag)). Cat timp e asa, NU exista nicio protectie: nici oprirea automata a scriptului, nici plasa de 20% a Windows-ului nu se pot declansa. De verificat, in ordine: cablul USB, portul, alimentarea UPS-ului, apoi Device Manager -> Batteries -> "HID UPS Battery". "@) | Out-Null $State.commsState = 'lost' $State.lastCommsAlert = $now.ToString('o') } } return $State # fara comunicatie nu luam nicio decizie de oprire } if ($script:CommsMissCount -gt 0 -or $State.commsState -eq 'lost') { if ($State.commsState -eq 'lost') { Write-Log 'Comunicatia cu UPS-ul a revenit' Send-UpsMail -Config $Config ` -Subject "[UPS] $env:COMPUTERNAME - comunicatia cu UPS-ul a revenit" ` -Body (Format-Reading -Reading $r -Config $Config) | Out-Null } $script:CommsMissCount = 0 $State.commsState = 'ok' $State.lastCommsAlert = $null } if ($State.commsState -eq 'unknown') { $State.commsState = 'ok' } # --- trecerea pe baterie / revenirea curentului ------------------------- if ($r.OnBattery) { $script:OnBatteryCount++ if ($State.powerState -ne 'battery') { $State.powerState = 'battery' $State.onBatterySince = $now.ToString('o') $State.shutdownTriggered = $false Write-Log "PANA DE CURENT - trecut pe baterie ($($r.Percent)%)" -Level ALERT Send-UpsMail -Config $Config ` -Subject "[UPS] PANA DE CURENT - $env:COMPUTERNAME a trecut pe baterie" ` -Body ((Format-Reading -Reading $r -Config $Config) + @" Daca curentul nu revine, serverul va fi oprit curat la primul prag atins. Urmeaza un email si atunci. "@) | Out-Null } $since = [datetime]$State.onBatterySince $minutes = (New-TimeSpan -Start $since -End $now).TotalMinutes if (-not $State.shutdownTriggered -and $script:OnBatteryCount -ge $Config.confirmPolls) { $reason = $null if ($minutes -ge $Config.shutdownAfterMinutesOnBattery) { $reason = "$([math]::Round($minutes)) minute pe baterie (prag: $($Config.shutdownAfterMinutesOnBattery))" } elseif ($null -ne $r.Percent -and $r.Percent -le $Config.shutdownAtBatteryPercent) { $reason = "incarcare $($r.Percent)% (prag: $($Config.shutdownAtBatteryPercent)%)" } elseif ($Config.shutdownAtRuntimeSeconds -gt 0 -and $null -ne $r.RuntimeSec -and $r.RuntimeSec -le $Config.shutdownAtRuntimeSeconds) { $reason = "autonomie estimata $([math]::Round($r.RuntimeSec / 60, 1)) min (prag: $([math]::Round($Config.shutdownAtRuntimeSeconds / 60, 1)) min)" } if ($reason) { $State.shutdownTriggered = $true Save-State -State $State -Path $Config.stateFile Invoke-UpsShutdown -Config $Config -Reading $r -Reason $reason -DryRun:$DryRun } } } else { $script:OnBatteryCount = 0 if ($State.powerState -eq 'battery') { $durata = if ($State.onBatterySince) { '{0:N1} minute' -f (New-TimeSpan -Start ([datetime]$State.onBatterySince) -End $now).TotalMinutes } else { 'necunoscuta' } Write-Log "Curentul a revenit dupa $durata" Send-UpsMail -Config $Config ` -Subject "[UPS] Curentul a revenit - $env:COMPUTERNAME" ` -Body ("Serverul a stat pe baterie $durata.`n`n" + (Format-Reading -Reading $r -Config $Config)) | Out-Null } $State.powerState = 'online' $State.onBatterySince = $null $State.shutdownTriggered = $false } return $State } # --------------------------------------------------------------------------- # Punctul de intrare # --------------------------------------------------------------------------- $cfg = Get-Config -Path $ConfigPath $script:LogDir = $cfg.logPath $script:SimPath = Join-Path (Split-Path -Parent $ConfigPath) 'SIMULARE.json' $script:OnBatteryCount = 0 $script:CommsMissCount = 0 switch ($Mode) { 'Status' { $r = Get-PowerReading Write-Host (Format-Reading -Reading $r -Config $cfg) Write-Host '' $st = Get-State -Path $cfg.stateFile Write-Host "Stare salvata : power=$($st.powerState) comms=$($st.commsState) onBatterySince=$($st.onBatterySince)" if (Test-Path -LiteralPath $script:SimPath) { Write-Host "SIMULARE : ACTIVA -> $script:SimPath" Get-Content -LiteralPath $script:SimPath -Raw | ForEach-Object { " $_" } } if (Test-Path -LiteralPath (Join-Path (Split-Path -Parent $ConfigPath) 'DEZACTIVAT')) { Write-Host 'AUTOMATISM : DEZARMAT (exista fisierul DEZACTIVAT)' } # "nu exista" si "n-am putut intreba" sunt lucruri diferite. Task Scheduler # refuza ocazional interogarea (vazut pe 27.08.2026: un NEINREGISTRAT intre # doua rulari care raportau corect Running), iar mesajul asta in mijlocul unei # pene ar trimite pe cineva sa reinstaleze un monitor care de fapt merge. $numeTask = 'ROMFAST UPS Monitor' try { $task = Get-ScheduledTask -TaskName $numeTask -ErrorAction SilentlyContinue if ($null -eq $task) { Write-Host 'Task programat : NEINREGISTRAT' } else { $info = Get-ScheduledTaskInfo -TaskName $numeTask -ErrorAction SilentlyContinue if ($null -eq $info) { Write-Host "Task programat : $($task.State)" } else { Write-Host "Task programat : $($task.State), ultima rulare $($info.LastRunTime), rezultat $($info.LastTaskResult)" } } } catch { Write-Host "Task programat : NEDETERMINAT - interogarea a esuat ($($_.Exception.Message))" } $log = Join-Path $cfg.logPath ('ups-monitor-{0}.log' -f (Get-Date -Format 'yyyy-MM')) if (Test-Path -LiteralPath $log) { Write-Host '' Write-Host "Ultimele linii din $log :" Get-Content -LiteralPath $log -Tail 15 | ForEach-Object { " $_" } } } 'TestMail' { $r = Get-PowerReading $ok = Send-UpsMail -Config $cfg ` -Subject "[UPS] Test - monitorizare $env:COMPUTERNAME" ` -Body ("Email de test din ups-monitor.ps1. Daca il primesti, alertarea functioneaza.`n`n" + (Format-Reading -Reading $r -Config $cfg)) if (-not $ok) { exit 1 } } 'TestShutdown' { $r = Get-PowerReading Invoke-UpsShutdown -Config $cfg -Reading $r -Reason 'TEST (dry-run, nu se opreste nimic)' -DryRun } 'Once' { $st = Get-State -Path $cfg.stateFile $st = Invoke-Cycle -Config $cfg -State $st Save-State -State $st -Path $cfg.stateFile Write-Host (Format-Reading -Reading (Get-PowerReading) -Config $cfg) } 'Run' { Write-Log "Pornit (interval $($cfg.pollSeconds)s, praguri: $($cfg.shutdownAfterMinutesOnBattery) min / $($cfg.shutdownAtBatteryPercent)%)" $st = Get-State -Path $cfg.stateFile $disableMarker = Join-Path (Split-Path -Parent $ConfigPath) 'DEZACTIVAT' $wasDisabled = $false while ($true) { try { if ((Test-Path -LiteralPath $disableMarker) -or -not $cfg.enabled) { if (-not $wasDisabled) { Write-Log 'Monitorizare DEZACTIVATA (marcaj DEZACTIVAT sau enabled=false) - doar astept' -Level WARN $wasDisabled = $true } } else { if ($wasDisabled) { Write-Log 'Monitorizare reactivata' $wasDisabled = $false } $st = Invoke-Cycle -Config $cfg -State $st Save-State -State $st -Path $cfg.stateFile } } catch { Write-Log "Eroare in ciclu: $($_.Exception.Message)" -Level ERROR } Start-Sleep -Seconds $cfg.pollSeconds } } }