feat(ups): ramura "pe baterie" se poate testa fara scoaterea din priza
Lantul de protectie are doua jumatati. Prima - "UPS-ul chiar raporteaza trecerea pe baterie?" - e deja dovedita de perechile de evenimente Kernel-Power 105 de la panele reale din 2026. A doua - decizie, alerta, oprirea curata a Oracle - nu avea cum sa fie exercitata decat asteptand o pana. Acum monitorul accepta un SIMULARE.json care suprascrie citirea de alimentare. Doua garzi, ca un fisier uitat sa nu opreasca serverul pe date inventate: "expira" e obligatoriu si e sters automat la depasire, iar oprirea e in gol daca nu se cere explicit "dryRun": false. Cat e activa, fiecare ciclu scrie WARN in log si -Mode Status o afiseaza. test-battery.ps1 conduce simularea si urmareste logul; sterge fisierul si daca e intrerupt. Cu -ConfirmReal 'DA-OPRESTE-SERVERUL' devine repetitie reala pentru o fereastra de mentenanta - singurul mod de a masura cat dureaza shutdown immediate pe baza de productie, informatie de care depinde alegerea pragurilor. Validat in gol pe 27.08.2026: detectie la 15:36:09 + email de pana, apoi exact trei cicluri de confirmare, declansare pe pragul de 35% la 15:36:40 si secventa parcursa integral. Oracle a ramas Running. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RRyaDj39hPQ89SZS6URRpS
This commit is contained in:
@@ -96,6 +96,7 @@ canal care să se strice separat.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `scripts/ups-monitor/ups-monitor.ps1` | monitorul propriu-zis (buclă + moduri de test) |
|
| `scripts/ups-monitor/ups-monitor.ps1` | monitorul propriu-zis (buclă + moduri de test) |
|
||||||
| `scripts/ups-monitor/install-ups-monitor.ps1` | instalare: copiere, `config.json`, ACL, task programat |
|
| `scripts/ups-monitor/install-ups-monitor.ps1` | instalare: copiere, `config.json`, ACL, task programat |
|
||||||
|
| `scripts/ups-monitor/test-battery.ps1` | test: simulează „pe baterie" fără scoaterea din priză |
|
||||||
| `scripts/ups-monitor/test-comms-loss.ps1` | test: dezactivează temporar bateria HID și verifică alerta de comunicație |
|
| `scripts/ups-monitor/test-comms-loss.ps1` | test: dezactivează temporar bateria HID și verifică alerta de comunicație |
|
||||||
| `scripts/ups-monitor/config.example.json` | șablon; **fișierul real nu se comite** (conține parola SMTP) |
|
| `scripts/ups-monitor/config.example.json` | șablon; **fișierul real nu se comite** (conține parola SMTP) |
|
||||||
|
|
||||||
@@ -170,9 +171,45 @@ powershell -ExecutionPolicy Bypass -File C:\ROMFAST\ups-monitor\ups-monitor.ps1
|
|||||||
| Pierderea comunicației (`test-comms-loss.ps1`) | dispozitiv oprit 15:24:44 → alertă **15:26:44** (exact 8 cicluri × 15 s) + email; reactivare → „comunicația a revenit" + email; `Win32_Battery` înapoi la 1 instanță |
|
| Pierderea comunicației (`test-comms-loss.ps1`) | dispozitiv oprit 15:24:44 → alertă **15:26:44** (exact 8 cicluri × 15 s) + email; reactivare → „comunicația a revenit" + email; `Win32_Battery` înapoi la 1 instanță |
|
||||||
| Taskul rulează ca SYSTEM | proces `ups-monitor.ps1 -Mode Run`, `state.json` actualizat la fiecare ciclu |
|
| Taskul rulează ca SYSTEM | proces `ups-monitor.ps1 -Mode Run`, `state.json` actualizat la fiecare ciclu |
|
||||||
|
|
||||||
**Netestat, pentru că cere pană reală:** trecerea pe baterie, pragurile de oprire și oprirea
|
### Testul „pe baterie", fără scoaterea din priză
|
||||||
efectivă a Oracle. Ramura de decizie e comună cu cea testată în dry-run, dar declanșatorul nu
|
|
||||||
a fost exercitat pe hardware. Vezi punctul 1 din secțiunea 6.
|
Lanțul de protecție are două jumătăți, și se testează separat:
|
||||||
|
|
||||||
|
| Jumătatea | Cum se validează |
|
||||||
|
|---|---|
|
||||||
|
| UPS → Windows (*chiar raportează trecerea pe baterie?*) | **deja dovedită de istoric**: perechile de evenimente Kernel-Power **105** la panele reale din 10 iun, 13 mai, 5 mai, 25 feb 2026 |
|
||||||
|
| Windows → script → alertă → oprirea Oracle | `test-battery.ps1`, oricând, fără hardware |
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# repetiție în gol - sigură oricând, în producție
|
||||||
|
powershell -ExecutionPolicy Bypass -File C:\ROMFAST\_deploy\test-battery.ps1 -Percent 30 -Minute 3
|
||||||
|
|
||||||
|
# doar trecerea pe baterie, fără atingerea pragului de oprire
|
||||||
|
powershell -ExecutionPolicy Bypass -File C:\ROMFAST\_deploy\test-battery.ps1 -Percent 80 -Minute 3
|
||||||
|
```
|
||||||
|
|
||||||
|
Scriptul scrie `SIMULARE.json` lângă `config.json`; cât există, monitorul folosește valorile
|
||||||
|
din el în locul citirii reale. Două gărzi, ca un fișier uitat să nu oprească serverul pe date
|
||||||
|
inventate:
|
||||||
|
|
||||||
|
- **`expira` e obligatoriu.** Fără el fișierul e ignorat; la depășire, monitorul îl șterge singur.
|
||||||
|
- **Oprirea e în gol**, dacă nu se cere explicit `"dryRun": false`.
|
||||||
|
|
||||||
|
Cât e activă simularea, fiecare ciclu scrie în log o linie `WARN SIMULARE ACTIVA`, iar
|
||||||
|
`-Mode Status` o afișează. `test-battery.ps1` șterge fișierul și dacă e întrerupt.
|
||||||
|
|
||||||
|
**Repetiție reală**, într-o fereastră de mentenanță — Oracle chiar se oprește și serverul
|
||||||
|
chiar se închide, dar UPS-ul rămâne în priză:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File C:\ROMFAST\_deploy\test-battery.ps1 -ConfirmReal 'DA-OPRESTE-SERVERUL'
|
||||||
|
```
|
||||||
|
|
||||||
|
Asta e singurul mod de a verifica de-adevăratelea cât durează `shutdown immediate` pe baza de
|
||||||
|
producție — informația de care depinde alegerea pragurilor.
|
||||||
|
|
||||||
|
**Rămâne netestabilă prin simulare** doar autonomia reală a UPS-ului: câte minute ține sub
|
||||||
|
sarcina actuală. Aceea cere scoaterea din priză (secțiunea 6, punctul 1).
|
||||||
|
|
||||||
**Dezarmare de urgență** (monitorul rămâne pornit, dar nu mai ia nicio decizie):
|
**Dezarmare de urgență** (monitorul rămâne pornit, dar nu mai ia nicio decizie):
|
||||||
|
|
||||||
@@ -252,11 +289,16 @@ nu are constrângerea asta.
|
|||||||
## 6. Rămâne de făcut
|
## 6. Rămâne de făcut
|
||||||
|
|
||||||
1. **Testul de autonomie reală.** Pragurile de 10 min / 35 % sunt alese conservator, nu
|
1. **Testul de autonomie reală.** Pragurile de 10 min / 35 % sunt alese conservator, nu
|
||||||
măsurate. Nu se știe cât ține UPS-ul sub sarcina actuală, deci nu se știe dacă cele 10
|
măsurate. Sunt două necunoscute distincte, iar doar una cere scoaterea din priză:
|
||||||
minute lasă destul timp pentru `shutdown immediate` + oprirea Windows-ului. Se măsoară
|
|
||||||
scoțând UPS-ul din priză, cu monitorul **dezarmat** (`DEZACTIVAT`) și cu cineva care
|
- *cât durează oprirea curată* (`shutdown immediate` + închiderea Windows-ului) — se află
|
||||||
urmărește `-Mode Status`. Până atunci, pragurile sunt o presupunere rezonabilă, nu o
|
dintr-o **repetiție reală** cu `test-battery.ps1 -ConfirmReal ...`, într-o fereastră de
|
||||||
garanție.
|
mentenanță, cu UPS-ul în priză;
|
||||||
|
- *cât ține UPS-ul sub sarcina actuală* — asta chiar cere scoaterea din priză, cu monitorul
|
||||||
|
**dezarmat** (`DEZACTIVAT`) și cu cineva care urmărește `-Mode Status`.
|
||||||
|
|
||||||
|
Pragul corect e „autonomia măsurată minus durata opririi, cu marjă". Până când ambele sunt
|
||||||
|
măsurate, pragurile rămân o presupunere rezonabilă, nu o garanție.
|
||||||
2. **Vârsta acumulatorilor.** Necunoscută. Pe cluster există deja criteriul de vârstă în
|
2. **Vârsta acumulatorilor.** Necunoscută. Pe cluster există deja criteriul de vârstă în
|
||||||
testul lunar (`proxmox/cluster/ups/`); aici nu există nimic echivalent.
|
testul lunar (`proxmox/cluster/ups/`); aici nu există nimic echivalent.
|
||||||
3. **BIOS: *Restore on AC power loss*.** De verificat la prima intervenție fizică — altfel
|
3. **BIOS: *Restore on AC power loss*.** De verificat la prima intervenție fizică — altfel
|
||||||
|
|||||||
121
scripts/ups-monitor/test-battery.ps1
Normal file
121
scripts/ups-monitor/test-battery.ps1
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Testeaza ramura "pe baterie" fara sa scoti UPS-ul din priza: scrie SIMULARE.json,
|
||||||
|
urmareste logul, apoi curata dupa el.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Se ruleaza PE SERVER, ridicat (Administrator), cu monitorul deja pornit.
|
||||||
|
|
||||||
|
Implicit, oprirea e parcursa IN GOL: se trimit emailurile, se scriu in log toti
|
||||||
|
pasii, dar nu se opreste nici Oracle, nici Windows. Asta valideaza tot lantul
|
||||||
|
decizie -> alerta -> secventa, adica exact partea pe care o pana reala ar
|
||||||
|
exercita-o.
|
||||||
|
|
||||||
|
Ce NU acopera: faptul ca Windows chiar raporteaza trecerea pe baterie. Acela e
|
||||||
|
deja dovedit de perechile de evenimente Kernel-Power 105 de la panele reale
|
||||||
|
(10 iun, 13 mai, 5 mai 2026) - vezi docs/ups-server36-monitorizare.md.
|
||||||
|
|
||||||
|
Simularea expira singura si e stearsa de monitor; scriptul o sterge oricum la
|
||||||
|
final, inclusiv daca e intrerupt.
|
||||||
|
|
||||||
|
.PARAMETER Percent
|
||||||
|
Incarcarea raportata. Sub pragul shutdownAtBatteryPercent (implicit 35),
|
||||||
|
declansarea vine dupa confirmPolls cicluri - adica in ~45 de secunde.
|
||||||
|
|
||||||
|
.PARAMETER Minute
|
||||||
|
Cat timp ramane activa simularea.
|
||||||
|
|
||||||
|
.PARAMETER ConfirmReal
|
||||||
|
Repetitia exacta 'DA-OPRESTE-SERVERUL' transforma testul intr-o repetitie
|
||||||
|
REALA: Oracle chiar se opreste si Windows chiar se inchide. De folosit doar
|
||||||
|
intr-o fereastra de mentenanta anuntata.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# repetitie in gol - sigura oricand
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\test-battery.ps1
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# doar trecerea pe baterie, fara sa se atinga pragul de oprire
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\test-battery.ps1 -Percent 80 -Minute 3
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# repetitie REALA, in fereastra de mentenanta: opreste Oracle si serverul
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\test-battery.ps1 -ConfirmReal 'DA-OPRESTE-SERVERUL'
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$InstallDir = 'C:\ROMFAST\ups-monitor',
|
||||||
|
[ValidateRange(0, 100)][int]$Percent = 30,
|
||||||
|
[ValidateRange(1, 60)][int]$Minute = 4,
|
||||||
|
[string]$ConfirmReal
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version 2.0
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$simPath = Join-Path $InstallDir 'SIMULARE.json'
|
||||||
|
$real = ($ConfirmReal -eq 'DA-OPRESTE-SERVERUL')
|
||||||
|
|
||||||
|
if ($ConfirmReal -and -not $real) {
|
||||||
|
throw "-ConfirmReal trebuie sa fie exact 'DA-OPRESTE-SERVERUL'. Am primit: '$ConfirmReal'"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath (Join-Path $InstallDir 'DEZACTIVAT')) {
|
||||||
|
Write-Warning 'Exista fisierul DEZACTIVAT: monitorul nu evalueaza nimic, deci testul nu va arata nimic.'
|
||||||
|
}
|
||||||
|
|
||||||
|
$cfg = Get-Content -LiteralPath (Join-Path $InstallDir 'config.json') -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
|
$expira = (Get-Date).AddMinutes($Minute)
|
||||||
|
|
||||||
|
if ($real) {
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host '*** REPETITIE REALA: Oracle SE VA OPRI si serverul SE VA INCHIDE. ***' -ForegroundColor Red
|
||||||
|
Write-Host ''
|
||||||
|
} else {
|
||||||
|
Write-Host 'Repetitie in gol: emailuri si log da, oprire efectiva nu.'
|
||||||
|
}
|
||||||
|
Write-Host ("Simulez: pe baterie, incarcare $Percent%. Prag de oprire: {0}% sau {1} min." -f `
|
||||||
|
$cfg.shutdownAtBatteryPercent, $cfg.shutdownAfterMinutesOnBattery)
|
||||||
|
Write-Host ("Simularea expira la {0:HH:mm:ss}." -f $expira)
|
||||||
|
Write-Host ''
|
||||||
|
|
||||||
|
$sim = [ordered]@{
|
||||||
|
onBattery = $true
|
||||||
|
upsPresent = $true
|
||||||
|
percent = $Percent
|
||||||
|
runtimeSec = 600
|
||||||
|
dryRun = (-not $real)
|
||||||
|
expira = $expira.ToString('o')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sim | ConvertTo-Json | Set-Content -LiteralPath $simPath -Encoding UTF8
|
||||||
|
Write-Host "Scris $simPath"
|
||||||
|
|
||||||
|
$log = Join-Path $InstallDir ('log\ups-monitor-{0}.log' -f (Get-Date -Format 'yyyy-MM'))
|
||||||
|
$de = 0
|
||||||
|
if (Test-Path -LiteralPath $log) { $de = @(Get-Content -LiteralPath $log).Count }
|
||||||
|
|
||||||
|
$pana = (Get-Date).AddMinutes($Minute)
|
||||||
|
while ((Get-Date) -lt $pana) {
|
||||||
|
Start-Sleep -Seconds 15
|
||||||
|
if (Test-Path -LiteralPath $log) {
|
||||||
|
$linii = @(Get-Content -LiteralPath $log)
|
||||||
|
if ($linii.Count -gt $de) {
|
||||||
|
$linii[$de..($linii.Count - 1)] | ForEach-Object { " $_" }
|
||||||
|
$de = $linii.Count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $simPath)) {
|
||||||
|
Write-Host ' (simularea a expirat si a fost stearsa de monitor)'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Remove-Item -LiteralPath $simPath -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host 'Simulare oprita, citirea reala e din nou activa.'
|
||||||
|
Start-Sleep -Seconds 20
|
||||||
|
Write-Host '--- stare dupa test ---'
|
||||||
|
& powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $InstallDir 'ups-monitor.ps1') -Mode Status
|
||||||
|
}
|
||||||
@@ -113,6 +113,61 @@ function Get-PowerReading {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 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 {
|
function Get-UpsDeviceInfo {
|
||||||
# Confirmare a doua, din WMI - folosita doar in alerte si in modul Status,
|
# Confirmare a doua, din WMI - folosita doar in alerte si in modul Status,
|
||||||
# ca sa nu interogam WMI la fiecare ciclu.
|
# ca sa nu interogam WMI la fiecare ciclu.
|
||||||
@@ -425,6 +480,17 @@ function Invoke-Cycle {
|
|||||||
$now = Get-Date
|
$now = Get-Date
|
||||||
$r = Get-PowerReading
|
$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 ---------------------------------------------
|
# --- comunicatia cu UPS-ul ---------------------------------------------
|
||||||
if (-not $r.UpsPresent) {
|
if (-not $r.UpsPresent) {
|
||||||
$script:CommsMissCount++
|
$script:CommsMissCount++
|
||||||
@@ -529,6 +595,7 @@ Urmeaza un email si atunci.
|
|||||||
|
|
||||||
$cfg = Get-Config -Path $ConfigPath
|
$cfg = Get-Config -Path $ConfigPath
|
||||||
$script:LogDir = $cfg.logPath
|
$script:LogDir = $cfg.logPath
|
||||||
|
$script:SimPath = Join-Path (Split-Path -Parent $ConfigPath) 'SIMULARE.json'
|
||||||
$script:OnBatteryCount = 0
|
$script:OnBatteryCount = 0
|
||||||
$script:CommsMissCount = 0
|
$script:CommsMissCount = 0
|
||||||
|
|
||||||
@@ -540,6 +607,13 @@ switch ($Mode) {
|
|||||||
Write-Host ''
|
Write-Host ''
|
||||||
$st = Get-State -Path $cfg.stateFile
|
$st = Get-State -Path $cfg.stateFile
|
||||||
Write-Host "Stare salvata : power=$($st.powerState) comms=$($st.commsState) onBatterySince=$($st.onBatterySince)"
|
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)'
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$task = Get-ScheduledTask -TaskName 'ROMFAST UPS Monitor' -ErrorAction Stop
|
$task = Get-ScheduledTask -TaskName 'ROMFAST UPS Monitor' -ErrorAction Stop
|
||||||
$info = Get-ScheduledTaskInfo -TaskName 'ROMFAST UPS Monitor'
|
$info = Get-ScheduledTaskInfo -TaskName 'ROMFAST UPS Monitor'
|
||||||
|
|||||||
Reference in New Issue
Block a user