# 把 Windows 被控端打包成「多文件 EXE」(PyInstaller --onedir) # 用法: powershell -ExecutionPolicy Bypass -File windows\build_exe.ps1 # # 产物: dist_exe\ykxmz\ # ├─ ykxmz.exe <- 双击运行 # └─ _internal\ <- 依赖 # # 刻意使用 --onedir 而不是 --onefile: # * 启动不解压临时文件,秒开(双击就弹窗口) # * 依赖可见、可查杀、可替换,方便做安装包 # # 注意:本版本**不再使用系统托盘**(pystray 已移除), # 关窗后进程继续运行,靠单实例机制再次双击找回窗口。 # # ============================================================================ # ⚠⚠ 必须用 Python 3.8 打包 —— 这是学校电脑能用的前提 ⚠⚠ # ============================================================================ # # 用户的学校电脑是 **Windows 7**,装完报「dll 缺失」。 # # 根因不是漏打包,是 **Python 版本越界**: # # Python 3.8 -> 最后一个支持 Windows 7 的版本 # Python 3.9+ -> 只支持 Windows 8 及以上 # # Python 3.9 起用到了 api-ms-win-core-path-l1-1-0.dll 里的 # PathCchCanonicalizeEx,而这个 DLL **Windows 7 上根本不存在**, # 也没法转发(api set 那套只在 Win8+ 生效)。 # # 所以用 3.11 打出来的 exe 在 Win7 上必然报缺 DLL。 # 那个 DLL 不是"忘了放进包",是"放进去也没用"——只能在 Win8+ 跑。 # # 这里选 **32 位** 3.8,因为 32 位在 Win7 的 32 位和 64 位系统上**都能跑**, # 一个包打天下,学校不用分两种发。 # (64 位的包在 32 位 Windows 上直接跑不起来。) # # 脚本会**主动校验**:拿到的 Python 不是 3.8 就直接报错退出, # 而不是默默打出一个人家装不上的包。 # ============================================================================ $ErrorActionPreference = "Continue" $windows = $PSScriptRoot # LanRemote/windows $root = Split-Path -Parent $windows # LanRemote # ---------------------------------------------------------------- 选 Python Write-Host "=== 0/4 选择 Python(必须是 3.8,为了 Windows 7)===" -ForegroundColor Cyan function Find-Py38 { # 1) 环境变量指定 if ($env:YKXMZ_PY -and (Test-Path $env:YKXMZ_PY)) { return $env:YKXMZ_PY } # 2) 常见安装位置 $cands = @( "C:\ykxmz-py38\python.exe", "C:\Python38\python.exe", "$env:LOCALAPPDATA\Programs\Python\Python38\python.exe", "$env:LOCALAPPDATA\Programs\Python\Python38-32\python.exe", "C:\Program Files (x86)\Python38-32\python.exe" ) foreach ($c in $cands) { if (Test-Path $c) { return $c } } # 3) py launcher try { $p = & py -3.8 -c "import sys;print(sys.executable)" 2>$null if ($LASTEXITCODE -eq 0 -and $p -and (Test-Path $p.Trim())) { return $p.Trim() } } catch { } return $null } $py = Find-Py38 if (-not $py) { Write-Host @" ✗ 找不到 Python 3.8。 这个项目**必须**用 3.8 打包,因为学校电脑是 Windows 7, 而 Python 3.9 及以上在 Win7 上会因为缺 api-ms-win-core-path-l1-1-0.dll 直接启动失败。 装一个(32 位,Win7 32/64 位都能用): https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe 静默安装到 C:\ykxmz-py38 : python-3.8.10.exe /quiet InstallAllUsers=0 TargetDir=C:\ykxmz-py38 `` Include_pip=1 Include_tcltk=1 Include_test=0 Include_launcher=0 再装依赖: C:\ykxmz-py38\python.exe -m pip install Pillow==9.5.0 pyinstaller==5.13.2 或者设环境变量 YKXMZ_PY 指向已有的 3.8。 "@ -ForegroundColor Red throw "需要 Python 3.8" } $ver = & $py -c "import sys;print('%d.%d'%sys.version_info[:2])" $bits = & $py -c "import struct;print(struct.calcsize('P')*8)" Write-Host " Python: $py" Write-Host " 版本: $ver ($bits 位)" if ($ver -ne "3.8") { throw "打包必须用 Python 3.8(当前 $ver)。用 3.9+ 打出来的包在 Windows 7 上会因为缺 api-ms-win-core-path-l1-1-0.dll 启动失败。" } if ($bits -ne "32") { Write-Host " ⚠ 不是 32 位 —— 64 位的包在 32 位 Windows(不少学校机器是)上跑不起来。" -ForegroundColor Yellow Write-Host " 建议换成 32 位的 3.8。继续打包……" -ForegroundColor Yellow } Write-Host "`n=== 1/4 生成图标 ===" -ForegroundColor Cyan & $py (Join-Path $root "_build\make_ico.py") if ($LASTEXITCODE -ne 0) { throw "生成图标失败" } $ico = Join-Path $windows "app.ico" Write-Host "`n=== 2/4 PyInstaller 打包(--onedir 多文件)===" -ForegroundColor Cyan # ⚠ lanremote.ui 必须显式 --hidden-import(见下面那行)。 # 它是"设置窗口"(tkinter)的实现,而静默那条路不需要它, # PyInstaller 静态分析就找不到 —— 于是双击 exe 想弹设置窗口时 # 会 ModuleNotFoundError: No module named 'lanremote.ui', # 进程直接退出,用户看到的就是"双击没反应"。 # (PS 的续行符 ` 后面不能再跟 # 注释,所以说明写在这里。) & $py -m PyInstaller --noconfirm --clean --onedir --windowed ` --name "ykxmz" ` --icon "$ico" ` --paths "$windows" ` --hidden-import "PIL._tkinter_finder" ` --hidden-import "lanremote.netcheck" ` --hidden-import "lanremote.features" ` --hidden-import "lanremote.headless" ` --hidden-import "lanremote.ui" ` --version-file (Join-Path $windows "version_info.txt") ` --noupx ` --exclude-module "pystray" ` --exclude-module "numpy" ` --exclude-module "psutil" ` --distpath (Join-Path $root "dist_exe") ` --workpath (Join-Path $root "_build\pyinstaller_work") ` --specpath (Join-Path $root "_build") ` (Join-Path $windows "run_agent.py") 2>&1 | Select-Object -Last 4 $target = Join-Path $root "dist_exe\ykxmz\ykxmz.exe" if (-not (Test-Path $target)) { throw "打包失败:未生成 $target" } # ---------------------------------------------------------------- Win7 校验 Write-Host "`n=== 3/4 Windows 7 兼容性校验 ===" -ForegroundColor Cyan & $py (Join-Path $root "_build\check_win7_compat.py") if ($LASTEXITCODE -ne 0) { throw "Win7 兼容性校验没过 —— 这个包学校电脑装不上,不该发布。" } Write-Host "`n=== 4/4 产物 ===" -ForegroundColor Cyan Write-Host "目录: $(Split-Path -Parent $target)" Write-Host ("exe : {0:N1} MB" -f ((Get-Item $target).Length / 1MB)) $all = Get-ChildItem (Split-Path -Parent $target) -Recurse -File Write-Host ("总计: {0} 个文件, {1:N1} MB" -f $all.Count, (($all | Measure-Object Length -Sum).Sum / 1MB)) Write-Host "`n完成(Python $ver $bits 位,可在 Windows 7 及以上运行)" -ForegroundColor Green