Release 0.4.13: elevate all end-user entry points for Program Files installs.

CMD and packaging scripts self-elevate via UAC; fix RunAs/WindowStyle clash; logon tray uses Highest scheduled task.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-09 14:55:40 +08:00
parent ee053f9990
commit afa8e06b2d
20 changed files with 193 additions and 50 deletions

View file

@ -63,6 +63,8 @@ Optional local zip only: `build_release.ps1 -CreateZip` (not for release upload)
5. Data → `%ProgramData%\NetX\` (`.env`, `pgdata`, spool, secrets).
6. Start menu: **Start NetX** / **Stop NetX** / **Open NetX UI** / **Reconfigure database**.
**Elevation (required on fresh PCs):** all end-user entry points (`NetX-Start.cmd`, `NetX-Tray.cmd`, `NetX-Stop.cmd`, `NetX-FirstRun.cmd`, tray, start/stop/setup/update) **self-elevate via UAC**. Program Files installs cannot start reliably as a normal user (`.venv` repair / writes). Approve the UAC prompt once per launch (tray stays elevated afterward).
Silent first install (bundled default): `/SILENT /DbMode=bundled`
Silent external: `/SILENT /DbMode=external /DbHost=... /DbPort=5432 /DbUser=... /DbPassword=... /DbName=...`
Silent update over existing data: `/VERYSILENT /NORESTART /InstallMode=update` (also `/SkipDbPage=1`)
@ -81,7 +83,7 @@ Existing Windows deploys that only set `NETX_DATABASE_URL` keep working: never s
## Manual update
Preferred: run a newer `NetX-Setup-*.exe` and choose **Update** (or silent `/InstallMode=update`) so ProgramData is kept. Setup (elevated) always relinks `.venv` → `python/runtime` after file copy so tray start works without write access under Program Files.
Preferred: run a newer `NetX-Setup-*.exe` and choose **Update** (or silent `/InstallMode=update`) so ProgramData is kept. Setup (elevated) always relinks `.venv` → `python/runtime` after file copy; runtime scripts also self-elevate so Start/Tray work on locked-down fresh PCs.
Legacy/dev zip (only if you built with `-CreateZip`):
@ -155,10 +157,11 @@ Token: `NETX_FORGEJO_TOKEN` (or `FORGEJO_TOKEN`).
## Tray & autostart
```powershell
# System tray: Start / Stop / Open UI / Check updates
# System tray: Start / Stop / Open UI / Check updates (self-elevates)
.\packaging\netx_tray.ps1 -StartOnLaunch
# Start tray at Windows logon (current user)
# Start tray at Windows logon — Scheduled Task with RunLevel Highest
# (replaces legacy HKCU\Run which cannot elevate on fresh installs)
.\packaging\install_autostart.ps1
# Remove: .\packaging\install_autostart.ps1 -Remove
```
@ -180,10 +183,10 @@ Uses [WinSW](https://github.com/winsw/winsw) (downloaded on first install into `
## Silent auto-update
```powershell
# Writes NETX_UPDATE_AUTO=true and registers a daily task (default 03:30)
# Writes NETX_UPDATE_AUTO=true and registers a daily task (default 03:30, RunLevel Highest)
.\packaging\install_update_task.ps1
# Manual silent path (only applies when NETX_UPDATE_AUTO=true)
# Manual silent path (only applies when NETX_UPDATE_AUTO=true; self-elevates)
.\packaging\check_update.ps1 -Apply -Quiet -AutoOnly
```

View file

@ -322,7 +322,8 @@ function Start-NetxPowerShell {
[ValidateSet("Hidden", "Normal", "Minimized")]
[string]$WindowStyle = "Hidden",
[switch]$Wait = $false,
[switch]$PassThru = $false
[switch]$PassThru = $false,
[switch]$RunAs = $false
)
$parts = @(
"-NoProfile",
@ -333,14 +334,104 @@ function Start-NetxPowerShell {
$sp = @{
FilePath = "powershell.exe"
ArgumentList = (ConvertTo-NetxProcessArgumentString -Arguments $parts)
WindowStyle = $WindowStyle
}
# -Verb RunAs implies UseShellExecute; cannot combine with -WindowStyle.
if (-not $RunAs) {
$sp.WindowStyle = $WindowStyle
}
if ($WorkingDirectory) { $sp.WorkingDirectory = $WorkingDirectory }
if ($Wait) { $sp.Wait = $true }
if ($PassThru -or $Wait) { $sp.PassThru = $true }
if ($RunAs) { $sp.Verb = "RunAs" }
return Start-Process @sp
}
function Test-NetxIsAdmin {
try {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
} catch {
return $false
}
}
function ConvertTo-NetxRelaunchArgumentList {
param([System.Collections.IDictionary]$BoundParameters)
$parts = [System.Collections.Generic.List[string]]::new()
if (-not $BoundParameters) { return @() }
foreach ($key in $BoundParameters.Keys) {
$val = $BoundParameters[$key]
if ($val -is [System.Management.Automation.SwitchParameter]) {
if ($val.IsPresent) { [void]$parts.Add("-$key") }
continue
}
if ($val -is [bool]) {
if ($val) { [void]$parts.Add("-$key") }
continue
}
if ($null -eq $val) { continue }
$s = [string]$val
if ($s -eq "") { continue }
[void]$parts.Add("-$key")
[void]$parts.Add($s)
}
return @($parts)
}
function Assert-NetxAdminOrRelaunch {
<#
.SYNOPSIS
End-user packaged scripts must run elevated (Program Files + ProgramData).
If not admin, re-launch the same script via UAC and exit with child exit code.
#>
param(
[Parameter(Mandatory = $true)][string]$ScriptPath,
[System.Collections.IDictionary]$BoundParameters = @{},
[string]$WorkingDirectory = "",
[ValidateSet("Hidden", "Normal", "Minimized")]
[string]$WindowStyle = "Normal"
)
if (Test-NetxIsAdmin) { return }
if ($env:NETX_ELEVATION_RELAUNCH -eq "1") {
throw "admin_required: still not elevated after UAC. Run NetX-Start / NetX-Tray as Administrator."
}
if (-not (Test-Path -LiteralPath $ScriptPath)) {
throw "elevation_script_missing: $ScriptPath"
}
$wd = $WorkingDirectory
if (-not $wd) { $wd = Split-Path -Parent $ScriptPath }
# Window style belongs on powershell.exe argv, not Start-Process (incompatible with -Verb RunAs).
$parts = @(
"-NoProfile",
"-WindowStyle", $WindowStyle,
"-ExecutionPolicy", "Bypass",
"-File", $ScriptPath
) + (ConvertTo-NetxRelaunchArgumentList -BoundParameters $BoundParameters)
$prevRel = $env:NETX_ELEVATION_RELAUNCH
$env:NETX_ELEVATION_RELAUNCH = "1"
try {
$p = Start-Process -FilePath "powershell.exe" `
-ArgumentList (ConvertTo-NetxProcessArgumentString -Arguments $parts) `
-WorkingDirectory $wd `
-Verb RunAs -Wait -PassThru
} catch {
if ($null -ne $prevRel) { $env:NETX_ELEVATION_RELAUNCH = $prevRel } else { Remove-Item Env:NETX_ELEVATION_RELAUNCH -ErrorAction SilentlyContinue }
throw ("admin_required: UAC cancelled or elevation failed. Run NetX as Administrator. {0}" -f $_.Exception.Message)
}
if ($null -ne $prevRel) { $env:NETX_ELEVATION_RELAUNCH = $prevRel } else { Remove-Item Env:NETX_ELEVATION_RELAUNCH -ErrorAction SilentlyContinue }
if ($null -eq $p) {
throw "admin_required: elevation failed (no process)"
}
$code = 0
if ($null -ne $p.ExitCode) { $code = [int]$p.ExitCode }
exit $code
}
function Get-NetxSystemPython {
# Prefer a real interpreter; skip the WindowsApps Store stub.
$candidates = @()

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[string]$UpdateUrl = "",
@ -29,6 +29,8 @@
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters `
-WindowStyle $(if ($Quiet) { "Hidden" } else { "Normal" })
$prog = Get-NetxProgramRoot -Override $ProgramRoot
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot

View file

@ -1,9 +1,14 @@
@echo off
setlocal
setlocal EnableDelayedExpansion
net session >nul 2>&1
if not "%errorLevel%"=="0" (
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "try { $p = Start-Process -FilePath '%~f0' -Verb RunAs -Wait -PassThru; if ($null -eq $p -or $null -eq $p.ExitCode) { exit 1 }; exit $p.ExitCode } catch { exit 1 }"
exit /b !ERRORLEVEL!
)
set "ROOT=%~dp0"
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
cd /d "%ROOT%"
title NetX — Reconfigure database
title NetX - Reconfigure database
echo.
echo NetX database reconfigure / 重新配置数据库
echo Prefer the installer wizard for first-time setup.
@ -12,12 +17,12 @@ echo This window is for repair / reconfigure only.
echo 本窗口仅用于修复或重新配置。
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%ROOT%\packaging\setup_first_run.ps1" -ProgramRoot "%ROOT%" -DataRoot "%ProgramData%\NetX"
set "EC=%ERRORLEVEL%"
if not "%EC%"=="0" (
set "EC=!ERRORLEVEL!"
if not "!EC!"=="0" (
echo.
echo Setup failed / 配置失败 exit=%EC%
echo Setup failed / 配置失败 exit=!EC!
pause
exit /b %EC%
exit /b !EC!
)
echo.
echo Starting NetX tray... / 正在启动托盘...

View file

@ -1,7 +1,13 @@
@echo off
setlocal
setlocal EnableDelayedExpansion
REM Always elevate: Program Files install needs admin to start API / repair venv.
net session >nul 2>&1
if not "%errorLevel%"=="0" (
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "try { $p = Start-Process -FilePath '%~f0' -Verb RunAs -Wait -PassThru; if ($null -eq $p -or $null -eq $p.ExitCode) { exit 1 }; exit $p.ExitCode } catch { exit 1 }"
exit /b !ERRORLEVEL!
)
set "ROOT=%~dp0"
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
cd /d "%ROOT%"
start "" powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "%ROOT%\packaging\launch_shortcut.ps1" -Action start -ProgramRoot "%ROOT%" -DataRoot "%ProgramData%\NetX"
exit /b 0
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%ROOT%\packaging\launch_shortcut.ps1" -Action start -ProgramRoot "%ROOT%" -DataRoot "%ProgramData%\NetX"
exit /b !ERRORLEVEL!

View file

@ -1,7 +1,12 @@
@echo off
setlocal
setlocal EnableDelayedExpansion
net session >nul 2>&1
if not "%errorLevel%"=="0" (
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "try { $p = Start-Process -FilePath '%~f0' -Verb RunAs -Wait -PassThru; if ($null -eq $p -or $null -eq $p.ExitCode) { exit 1 }; exit $p.ExitCode } catch { exit 1 }"
exit /b !ERRORLEVEL!
)
set "ROOT=%~dp0"
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
cd /d "%ROOT%"
start "" powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "%ROOT%\packaging\launch_shortcut.ps1" -Action stop -ProgramRoot "%ROOT%" -DataRoot "%ProgramData%\NetX"
exit /b 0
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%ROOT%\packaging\launch_shortcut.ps1" -Action stop -ProgramRoot "%ROOT%" -DataRoot "%ProgramData%\NetX"
exit /b !ERRORLEVEL!

View file

@ -1,7 +1,13 @@
@echo off
setlocal
setlocal EnableDelayedExpansion
REM Elevate once, then start tray (inherits admin for Start/Stop/Update).
net session >nul 2>&1
if not "%errorLevel%"=="0" (
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "try { $p = Start-Process -FilePath '%~f0' -Verb RunAs -Wait -PassThru; if ($null -eq $p -or $null -eq $p.ExitCode) { exit 1 }; exit $p.ExitCode } catch { exit 1 }"
exit /b !ERRORLEVEL!
)
set "ROOT=%~dp0"
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
cd /d "%ROOT%"
start "" powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "%ROOT%\packaging\launch_shortcut.ps1" -Action tray -ProgramRoot "%ROOT%" -DataRoot "%ProgramData%\NetX" -StartOnLaunch
start "" powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "%ROOT%\packaging\netx_tray.ps1" -ProgramRoot "%ROOT%" -DataRoot "%ProgramData%\NetX" -StartOnLaunch
exit /b 0

View file

@ -1,25 +1,41 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[switch]$Remove = $false
)
# Register / unregister NetX tray at current-user logon.
# Register / unregister NetX tray at logon with highest privileges (UAC admin).
# HKCU\Run cannot elevate reliably on fresh PCs; use a Scheduled Task instead.
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Normal
$prog = Get-NetxProgramRoot -Override $ProgramRoot
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot
$tray = Join-Path $PSScriptRoot "netx_tray.ps1"
$runKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
$name = "NetX"
$legacyName = "NetX"
$taskName = "NetXTray"
$taskPath = "\NetX\"
# Always clear legacy per-user Run key (non-elevated).
Remove-ItemProperty -Path $runKey -Name $legacyName -ErrorAction SilentlyContinue
if ($Remove) {
Remove-ItemProperty -Path $runKey -Name $name -ErrorAction SilentlyContinue
Write-Host "==> Removed NetX from startup"
Unregister-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Confirm:$false -ErrorAction SilentlyContinue
Write-Host "==> Removed NetX tray autostart task"
exit 0
}
$cmd = "powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$tray`" -ProgramRoot `"$prog`" -StartOnLaunch"
New-ItemProperty -Path $runKey -Name $name -Value $cmd -PropertyType String -Force | Out-Null
Write-Host "==> NetX tray will start at logon"
Write-Host " $cmd"
$arg = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$tray`" -ProgramRoot `"$prog`" -DataRoot `"$data`" -StartOnLaunch"
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $arg -WorkingDirectory $prog
$trig = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest
Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath `
-Action $action -Trigger $trig -Settings $settings -Principal $principal -Force | Out-Null
Write-Host "==> NetX tray will start at logon (Scheduled Task, RunLevel Highest)" -ForegroundColor Green
Write-Host " $taskPath$taskName"
Write-Host " $arg"

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[ValidateSet("winsw", "task")]
@ -15,6 +15,7 @@
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Normal
$prog = Get-NetxProgramRoot -Override $ProgramRoot
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot
@ -25,9 +26,7 @@ $winswXml = Join-Path $winswDir "NetX.xml"
$cacheDir = Join-Path $PSScriptRoot "cache"
function Test-IsAdmin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
return Test-NetxIsAdmin
}
function ConvertTo-WinSWPath([string]$Path) {

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[ValidateSet("Daily", "AtLogOn", "Hourly")]
@ -12,6 +12,7 @@
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Normal
$prog = Get-NetxProgramRoot -Override $ProgramRoot
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot
@ -51,7 +52,8 @@ switch ($Trigger) {
}
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -StartWhenAvailable
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Limited
# Highest: silent apply must write Program Files / run Setup.
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest
Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Action $action -Trigger $trig -Settings $settings -Principal $principal -Force | Out-Null
Write-Host "==> Scheduled update task: $taskPath$taskName ($Trigger)" -ForegroundColor Green
Write-Host "==> Scheduled update task: $taskPath$taskName ($Trigger, RunLevel Highest)" -ForegroundColor Green
Write-Host " Manual run: .\packaging\check_update.ps1 -Apply -Quiet -AutoOnly"

View file

@ -5,7 +5,7 @@
#define MyAppName "NetX"
#ifndef MyAppVersion
#define MyAppVersion "0.4.12"
#define MyAppVersion "0.4.13"
#endif
#define MyAppPublisher "NetX"
#define MyAppURL "https://github.com/hansjone/netx"

View file

@ -1,4 +1,4 @@
param(
param(
[ValidateSet("tray", "start", "stop", "setup", "update")]
[string]$Action = "tray",
[string]$ProgramRoot = "",
@ -11,6 +11,7 @@
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Hidden
Add-Type -AssemblyName System.Windows.Forms

View file

@ -1,11 +1,11 @@
{
"channel": "stable",
"latest": "0.4.12",
"latest": "0.4.13",
"min_compatible": "0.3.0",
"notes_url": "https://github.com/hansjone/netx/releases/tag/v0.4.12",
"notes_url": "https://github.com/hansjone/netx/releases/tag/v0.4.13",
"windows": {
"url": "https://github.com/hansjone/netx/releases/download/v0.4.12/NetX-Setup-0.4.12.exe",
"setup_url": "https://github.com/hansjone/netx/releases/download/v0.4.12/NetX-Setup-0.4.12.exe",
"url": "https://github.com/hansjone/netx/releases/download/v0.4.13/NetX-Setup-0.4.13.exe",
"setup_url": "https://github.com/hansjone/netx/releases/download/v0.4.13/NetX-Setup-0.4.13.exe",
"package": "setup",
"sha256": "",
"size": 0

View file

@ -7,9 +7,11 @@
)
# System-tray controller for NetX (Windows packaged installs).
# Always elevated: Start/Stop/Update touch Program Files + services.
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Hidden
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
@ -593,7 +595,7 @@ $miUpdate.add_Click({
)
try {
$ap = Start-Process -FilePath "powershell.exe" -ArgumentList $arg `
-WorkingDirectory $prog -WindowStyle Normal -Verb RunAs -PassThru
-WorkingDirectory $prog -Verb RunAs -PassThru
} catch {
$notify.ShowBalloonTip(4000, "NetX", (T "Update cancelled (UAC)." "已取消更新(UAC)。"), [System.Windows.Forms.ToolTipIcon]::Info)
return

View file

@ -3,9 +3,10 @@ param(
[string]$DataRoot = ""
)
# Relink shipped .venv to local python/runtime (run elevated after Setup/update).
# Relink shipped .venv to local python/runtime (must be elevated under Program Files).
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Hidden
$prog = Get-NetxProgramRoot -Override $ProgramRoot
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot

View file

@ -1,4 +1,4 @@
param(
param(
[ValidateSet("bundled", "external", "")]
[string]$DbMode = "",
[string]$ProgramRoot = "",
@ -15,6 +15,7 @@
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Normal
$prog = Get-NetxProgramRoot -Override $ProgramRoot
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[switch]$SkipBrowser = $false,
@ -8,6 +8,7 @@
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Normal
$prog = Get-NetxProgramRoot -Override $ProgramRoot
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot

View file

@ -7,6 +7,7 @@ param(
$ErrorActionPreference = "Continue"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Hidden
$prog = Get-NetxProgramRoot -Override $ProgramRoot
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot

View file

@ -1,4 +1,4 @@
param(
param(
[Parameter(Mandatory = $true)]
[string]$PackagePath,
[string]$ProgramRoot = "",
@ -9,6 +9,7 @@
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\_common.ps1"
Assert-NetxAdminOrRelaunch -ScriptPath $PSCommandPath -BoundParameters $PSBoundParameters -WindowStyle Normal
$prog = Get-NetxProgramRoot -Override $ProgramRoot
$data = Get-NetxDataRoot -ProgramRoot $prog -Override $DataRoot

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "netx-ops"
version = "0.4.12"
version = "0.4.13"
description = "netx operations tool: alarm-centric workflows with REST API and stdio MCP"
readme = "README.md"
requires-python = ">=3.11"