Fix Windows PowerShell parsing on Server 2012: UTF-8 BOM scripts.

Chinese packaging scripts without BOM were misread as ANSI and failed at first-run.
Document that bundled Python 3.13+ needs Server 2016+ / Win10+.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-07 10:53:29 +08:00
parent 8c35922432
commit 41ab4921a0
24 changed files with 48 additions and 28 deletions

View file

@ -21,6 +21,8 @@ This directory builds a Windows deliverable with:
**Offline:** Setup ships portable PostgreSQL, `.venv` (when built with `-CreateVenv`), and WinSW. First-run uses `-NonInteractive -DbMode bundled` — **no GitHub/EDB download on the target PC**. Service install uses bundled WinSW (pass `-AllowDownload` only on a build/dev machine if the binary is missing). Auto-update still needs network later, and is unchecked by default.
**OS:** Windows 10/11 or Windows Server **2016+** recommended. Packaging scripts are UTF-8 **with BOM** so Chinese UI works on Windows PowerShell 5.x. Bundled Python in current releases is **3.13+**, which does **not** support Windows Server 2012 R2 — use Server 2016+ or a newer desktop OS.
## Build (developer machine)
Prerequisites: Python 3.11+, Node 20+, PowerShell, network (to download PG binaries once).

View file

@ -1,4 +1,4 @@
# Shared path helpers for Windows packaging scripts.
# Shared path helpers for Windows packaging scripts.
# Dot-source: . "$PSScriptRoot\_common.ps1"
$ErrorActionPreference = "Stop"

View file

@ -1,4 +1,4 @@
param(
param(
[string]$Version = "",
[string]$OutDir = "",
[switch]$SkipWebBuild = $false,
@ -80,6 +80,22 @@ $webOut = Join-Path $stage "web\dist"
New-Item -ItemType Directory -Path (Split-Path $webOut -Parent) -Force | Out-Null
Copy-Item -Path $dist -Destination $webOut -Recurse -Force
function Write-Utf8BomFile {
param([string]$Path)
# Windows PowerShell 4/5 (Server 2012+) mis-parses UTF-8 without BOM when scripts contain CJK.
$utf8Bom = New-Object System.Text.UTF8Encoding $true
$bytes = [IO.File]::ReadAllBytes($Path)
$hasBom = ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF)
$text = if ($hasBom) {
[Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
} else {
[Text.Encoding]::UTF8.GetString($bytes)
}
$text = $text -replace "`r`n", "`n" -replace "`n", "`r`n"
if (-not $text.EndsWith("`r`n")) { $text += "`r`n" }
[IO.File]::WriteAllText($Path, $text, $utf8Bom)
}
$packOut = Join-Path $stage "packaging"
New-Item -ItemType Directory -Path $packOut -Force | Out-Null
Copy-Item -Path (Join-Path $PSScriptRoot "_common.ps1") -Destination $packOut -Force
@ -87,13 +103,15 @@ foreach ($name in @(
"download_postgres.ps1", "setup_first_run.ps1", "start_netx_app.ps1",
"stop_netx_app.ps1", "update_netx.ps1", "check_update.ps1",
"launch_shortcut.ps1", "netx_tray.ps1", "install_autostart.ps1", "install_service.ps1",
"service_run.ps1", "install_update_task.ps1", "sign_release.ps1",
"build_release.ps1", "publish_release.ps1", "publish_forgejo_release.ps1", "README.md", "manifest.example.json"
)) {
$src = Join-Path $PSScriptRoot $name
if (Test-Path $src) { Copy-Item $src (Join-Path $packOut $name) -Force }
}
Get-ChildItem -Path $packOut -Filter *.ps1 -File | ForEach-Object { Write-Utf8BomFile -Path $_.FullName }
Get-ChildItem -Path (Join-Path $stage "scripts") -Filter *.ps1 -File -ErrorAction SilentlyContinue |
ForEach-Object { Write-Utf8BomFile -Path $_.FullName }
Copy-Item -Path (Join-Path $PSScriptRoot "config") -Destination (Join-Path $packOut "config") -Recurse -Force
Copy-Item -Path (Join-Path $PSScriptRoot "installer") -Destination (Join-Path $packOut "installer") -Recurse -Force
$assetsSrc = Join-Path $PSScriptRoot "assets"

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[string]$UpdateUrl = "",

View file

@ -1,4 +1,4 @@
param(
param(
[string]$Version = "16.4-1",
[string]$OutDir = ""
)

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[switch]$Remove = $false
)

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[ValidateSet("winsw", "task")]

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[ValidateSet("Daily", "AtLogOn", "Hourly")]

View file

@ -4,7 +4,7 @@
#define MyAppName "NetX"
#ifndef MyAppVersion
#define MyAppVersion "0.4.2"
#define MyAppVersion "0.4.3"
#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 = "",

View file

@ -1,11 +1,11 @@
{
{
"channel": "stable",
"latest": "0.4.2",
"latest": "0.4.3",
"min_compatible": "0.3.0",
"notes_url": "https://github.com/hansjone/netx/releases/tag/v0.4.2",
"notes_url": "https://github.com/hansjone/netx/releases/tag/v0.4.3",
"windows": {
"url": "https://github.com/hansjone/netx/releases/download/v0.4.2/NetX-0.4.2-win64.zip",
"setup_url": "https://github.com/hansjone/netx/releases/download/v0.4.2/NetX-Setup-0.4.2.exe",
"url": "https://github.com/hansjone/netx/releases/download/v0.4.3/NetX-0.4.3-win64.zip",
"setup_url": "https://github.com/hansjone/netx/releases/download/v0.4.3/NetX-Setup-0.4.3.exe",
"sha256": "",
"size": 0
}

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[switch]$StartOnLaunch = $true,

View file

@ -1,4 +1,4 @@
param(
param(
[string]$Version = "",
[string]$ForgejoBase = "http://10.0.0.131:3000",
[string]$Owner = "hansjone",

View file

@ -1,4 +1,4 @@
param(
param(
[string]$Version = "",
[switch]$SkipBuild = $false,
[switch]$SkipInstaller = $false,

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[int]$HealthFailLimit = 6,

View file

@ -1,4 +1,4 @@
param(
param(
[ValidateSet("bundled", "external", "")]
[string]$DbMode = "",
[string]$ProgramRoot = "",

View file

@ -1,4 +1,4 @@
param(
param(
[Parameter(Mandatory = $true)]
[string[]]$Files,
[string]$Thumbprint = "",

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[switch]$SkipBrowser = $false,

View file

@ -1,4 +1,4 @@
param(
param(
[string]$ProgramRoot = "",
[string]$DataRoot = "",
[switch]$KeepPostgres = $false

View file

@ -1,4 +1,4 @@
param(
param(
[Parameter(Mandatory = $true)]
[string]$PackagePath,
[string]$ProgramRoot = "",