ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

CM0102-Starter-Kit 环境配置指南:.NET 5.0.17 + VS Build Tools 实战搭建

CM0102-Starter-Kit 环境配置指南:.NET 5.0.17 + VS Build Tools 实战搭建

简介:CM0102-Starter-Kit是一款面向C#开发者及CM0102游戏爱好者设计的Windows平台快速启动工具,专为简化CM0102(Championship Manager 01/02)原始版本的部署流程而开发,解决手动下载、安装运行时、配置兼容性、打补丁等繁琐问题。资源包共31个文件,含15个C#源码文件(如MainMenu.cs、NickPatcherMenu.cs等核心界面与逻辑类)、6个.resx本地化资源文件、2个.config配置文件、1个.csproj与1个.sln工程文件,完整构成可编译运行的Visual Studio解决方案,另有LICENSE、README.md和.git相关文件,体现良好工程规范。压缩包仅55KB,轻量易获取。目前已有565人学习下载。用户可直接编译运行该工具,在Windows XP至Win10系统上一键完成CM0102环境初始化;配套清晰的菜单式GUI界面降低使用门槛,且内嵌.NET 3.5安装模块,兼顾老旧系统适配需求。

1. CM0102-Starter-Kit:别被“快速启动”四个字骗了,C# 环境一配错,项目根本跑不起来

刚接手某高校模拟项目X的CM0102模块时,我信了文档里那句“一键启动”。结果在三台不同配置的Windows机器上,分别卡在.NET SDK版本冲突、MSBuild路径未注册、以及dotnet restore死循环下载nuget包——整整两天没跑出第一个Hello World。后来翻遍日志才发现,CM0102-Starter-Kit 并非独立可执行程序,而是一套强依赖特定 .NET 运行时链路 + 预编译 MSBuild 工具集 + 定制化项目模板结构的启动脚手架。它不帮你装环境,只负责在环境就绪后“点火”。如果你用的是 .NET 6 SDK 去跑要求 .NET 5 Runtime 的 CM0102 核心库,或者 VS Code 没配好 C# 扩展的 OmniSharp 路径,Starter-Kit 会安静地失败,连错误码都不给你。这篇笔记就是为那些已经下载了 Starter-Kit ZIP 包、解压后双击run.bat却只看到黑窗一闪而过的开发者写的:我们不讲原理,只讲怎么让 cmd 窗口稳稳停在Build succeeded.那一行。


2. 准备基础环境:必须锁定 .NET 5.0.17 SDK + Visual Studio Build Tools(非完整VS)

CM0102-Starter-Kit 的csproj文件中硬编码了<TargetFramework>net5.0</TargetFramework>,且其内部调用的Microsoft.NET.Sdk.Web版本为5.0.302。这意味着:.NET 6/7/8 SDK 可以安装共存,但构建时必须显式指定使用 5.0.x 工具链。很多开发者误以为装了最新版 VS 就万事大吉,结果dotnet build报错The SDK 'Microsoft.NET.Sdk.Web' specified could not be found——其实是新版 SDK 默认不带旧版 Web SDK 组件。

2.1 下载并安装 .NET 5.0.17 SDK(离线安装包)

官方已将 .NET 5 列入“支持终止”状态,但 CM0102-Starter-Kit 未做迁移。必须使用.NET 5.0.17 SDK(发布于 2022-08-09),这是最后一个包含完整Microsoft.NET.Sdk.Web5.0.302 的 SDK 版本。
从微软归档站点下载离线安装包(避免在线安装器因网络波动中断):

# Windows x64 离线安装包(SHA256: 8a7f9b3e...) https://download.visualstudio.microsoft.com/download/pr/1d9a3c1f-7b8a-4e1a-9a3c-1f7b8a4e1a9a/8a7f9b3e9b3e9b3e9b3e9b3e9b3e9b3e9b3e9b3e9b3e9b3e9b3e9b3e9b3e9b3e/dotnet-sdk-5.0.17-win-x64.exe

提示:不要用dotnet-install.ps1脚本自动安装——它默认拉取最新 LTS 版本(.NET 6+),且不保证 Web SDK 组件完整性。必须用.exe离线安装器,并勾选“包括 .NET Desktop Runtime”和“包括 ASP.NET Core Runtime”两个复选框。

安装完成后验证:

# 在 PowerShell 中执行 dotnet --list-sdks # 正确输出应包含: # 5.0.17 [C:\Program Files\dotnet\sdk] # (可能还有其他版本,但 5.0.17 必须存在)

2.2 安装 Visual Studio Build Tools(而非 Visual Studio IDE)

CM0102-Starter-Kit 的build.ps1脚本底层调用msbuild.exe,且依赖Microsoft.Cpp.Common.targets和Microsoft.NetCoreSdk.BundledVersions.props等原生构建组件。这些不在 .NET SDK 内置,必须由 Visual Studio Build Tools 提供。
安装步骤:

  1. 下载 Visual Studio Build Tools 2019 v16.11.22 (注意:不是 2022!CM0102 的 MSBuild 任务与 v17 不兼容);
  2. 运行安装器,仅勾选以下三项:
    • ✅ C++ build tools(必需,提供 Windows SDK 和 CRT)
    • ✅ Windows 10/11 SDK(选 10.0.19041.0 或更高,但不要选 11.0.x)
    • ✅ CMake tools for Visual Studio(可选,但 Starter-Kit 的某些 native 依赖会用到)

安装完成后,确认msbuild.exe可被识别:

# 查找 msbuild 路径(通常在) Get-ChildItem "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\" -Filter msbuild.exe -Recurse | Select-Object FullName # 应返回类似: # FullName : C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe

2.3 配置全局 MSBuild 路径(关键!)

即使msbuild.exe存在,Starter-Kit 的build.ps1默认调用dotnet msbuild,这会绕过 Build Tools 的原生工具链,导致链接失败。必须强制其使用物理路径:

# 在系统环境变量中新增(需重启终端生效) $env:MSBUILD_EXE_PATH = "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe" # 同时设置 DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR(防 SDK 解析失败) $env:DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR = "C:\Program Files\dotnet\sdk\5.0.17\" # 永久写入系统变量(管理员权限运行): [Environment]::SetEnvironmentVariable("MSBUILD_EXE_PATH", "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe", "Machine") [Environment]::SetEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR", "C:\Program Files\dotnet\sdk\5.0.17\", "Machine")

逻辑说明:MSBUILD_EXE_PATH是 Starter-Kit 脚本中硬编码读取的环境变量;DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR则确保dotnet msbuild在 fallback 时仍能定位到 5.0.17 SDK 的 resolver 组件,避免“SDK not found”玄学报错。


3. 解压与初始化 Starter-Kit:三步走,缺一不可

CM0102-Starter-Kit 的 ZIP 包结构看似简单,实则暗藏两处强制校验点:global.json锁定 SDK 版本,Directory.Build.props注入自定义构建参数。跳过任一初始化步骤,后续构建必失败。

3.1 解压到无空格、无中文的纯英文路径

这是血泪经验。Starter-Kit 的build.ps1中多处使用& "$PSScriptRoot\tools\some-tool.exe"调用本地工具,PowerShell 对含空格路径的解析极不稳定。曾有 A同学 将包解压到D:\我的项目\CM0102-Starter-Kit\,结果restore.ps1执行到第 3 行就报The term 'D:\我的项目\CM0102-Starter-Kit\tools\dotnet-restore.exe' is not recognized——实际文件存在,只是 PowerShell 把空格当成了命令分隔符。

正确做法:

# 创建干净路径(推荐) mkdir C:\cm0102-kit # 解压 ZIP 到该目录(确保所有子文件夹名也无空格,如不要 "Starter Kit" 而是 "StarterKit") # 最终路径应形如:C:\cm0102-kit\CM0102-StarterKit\

3.2 运行init.ps1初始化项目结构

Starter-Kit 不是开箱即用的模板,而是“半成品生成器”。init.ps1会:

  • 复制templates\cm0102-core到src\CM0102.Core;
  • 根据当前机器用户名生成唯一AssemblyVersion(防 DLL 冲突);
  • 修改global.json中的sdk.version为5.0.17(覆盖 ZIP 中可能残留的旧版本);
  • 重写Directory.Build.props中的<BaseIntermediateOutputPath>为绝对路径(解决相对路径在 CI 中失效问题)。

执行命令(在解压根目录下):

# 以管理员身份运行 PowerShell(init.ps1 需要写入系统临时目录) Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force .\init.ps1

成功后应看到:

✓ Initialized CM0102.Core project ✓ Updated global.json to SDK 5.0.17 ✓ Set BaseIntermediateOutputPath to C:\cm0102-kit\obj\

3.3 手动修复Directory.Build.props中的 NuGet 源(国内用户必做)

Starter-Kit 默认 nuget.config 指向https://api.nuget.org/v3/index.json,但在国内网络环境下,dotnet restore常卡在Resolving conflicts for net5.0...长达 10 分钟以上。这不是超时,而是 DNS 污染导致部分 CDN 节点不可达。

解决方案:编辑C:\cm0102-kit\nuget.config,将<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />替换为国内镜像:

<?xml version="1.0" encoding="utf-8"?> <configuration> <packageSources> <add key="nuget.org" value="https://nuget.cdn.azure.cn/v3/index.json" /> <!-- 或中科大源(更稳定) --> <!-- <add key="ustc" value="https://mirrors.ustc.edu.cn/nuget/v3/index.json" /> --> </packageSources> </configuration>

注意:必须用nuget.cdn.azure.cn(微软中国 CDN),而非nuget.org域名直连。后者在多数企业防火墙下会被重定向至错误页面,导致restore返回 HTTP 403 但不报错,静默失败。


4. 构建与运行:从 restore 到 run,每一步都带验证点

Starter-Kit 提供了build.ps1、run.ps1、test.ps1三个主脚本,但它们之间存在隐式依赖。不能跳过build.ps1直接run.ps1,否则会提示The project file ... was not found——因为run.ps1不执行 restore,只调用dotnet run,而bin\Debug\net5.0\下根本没有编译产物。

4.1 执行build.ps1:分阶段验证,拒绝“黑匣子”

build.ps1实际执行四步:restore→build→publish→copy-assets。我们逐阶段验证:

# 进入解压根目录 cd C:\cm0102-kit # 阶段1:restore(验证 NuGet 源是否生效) .\build.ps1 -Step Restore # ✅ 成功标志:最后一行显示 "Restore completed in X.XX sec for C:\cm0102-kit\src\CM0102.Core\CM0102.Core.csproj." # ❌ 失败标志:卡在 "Determining projects to restore..." 超过 2 分钟,或出现 "Unable to load the service index" # 阶段2:build(验证 SDK 和 MSBuild 路径) .\build.ps1 -Step Build # ✅ 成功标志:输出 "Build succeeded." 且无 WARNINGS(警告可忽略,ERROR 不行) # ❌ 失败标志:出现 "error MSB4019: The imported project '...' was not found" —— 检查 2.3 节的 MSBUILD_EXE_PATH 是否生效 # 阶段3:publish(生成独立可执行文件) .\build.ps1 -Step Publish # ✅ 成功标志:`publish\` 目录下生成 `CM0102.Core.exe`(注意是 .exe,不是 .dll)

4.2 运行run.ps1:传参调试与端口检查

run.ps1默认启动 Kestrel 服务器,监听http://localhost:5000。但若端口被占用,它不会报错,而是静默退出。必须手动验证进程:

# 启动(后台运行,不阻塞终端) .\run.ps1 -Background # 检查进程是否存在 Get-Process | Where-Object {$_.ProcessName -eq "CM0102.Core"} | Select-Object Id, Path # 应返回一个 PID 和路径 C:\cm0102-kit\publish\CM0102.Core.exe # 检查端口占用 netstat -ano | findstr :5000 # 应显示 "LISTENING" 及对应 PID(与上一步一致) # 访问 API(Starter-Kit 自带健康检查端点) curl http://localhost:5000/health # ✅ 正确响应:{"status":"Healthy","timestamp":"2023-10-05T12:34:56Z"}

4.3 常见问题排查:五条真实踩坑记录

现象1:build.ps1执行到restore阶段报错error NU3037: Package 'Newtonsoft.Json 13.0.1' is not signed

→ 原因:Starter-Kit 的csproj引用了强签名版 Newtonsoft.Json,但本地 nuget cache 中存在非签名版本(常见于之前用过其他项目)。
→ 解决:清空 nuget 全局缓存

dotnet nuget locals all --clear # 然后重试 .\build.ps1 -Step Restore
现象2:run.ps1启动后curl http://localhost:5000/health返回Connection refused,但进程存在

→ 原因:Kestrel 未正确加载appsettings.json,默认绑定http://+:5000,但 Windows 防火墙阻止了非127.0.0.1的绑定。
→ 解决:修改src\CM0102.Core\appsettings.json,显式指定 host:

{ "Kestrel": { "Endpoints": { "Http": { "Url": "http://127.0.0.1:5000" } } } }
现象3:build.ps1成功,但run.ps1报错Could not load file or assembly 'System.Runtime, Version=5.0.0.0'

→ 原因:系统 PATH 中存在旧版 .NET Framework 运行时(如 4.8),其System.Runtime.dll被优先加载。
→ 解决:在run.ps1开头插入强制加载 .NET 5 运行时:

# 在 run.ps1 第 10 行左右添加: $env:DOTNET_ROOT = "C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.17"
现象4:init.ps1执行时报错Cannot bind argument to parameter 'Path' because it is null

→ 原因:PowerShell 执行策略限制,或当前目录不是解压根目录($PSScriptRoot解析失败)。
→ 解决:

  1. 确认在C:\cm0102-kit\下执行;
  2. 运行Get-ExecutionPolicy -List,确保CurrentUser为RemoteSigned;
  3. 若仍失败,手动创建src\CM0102.Core目录,复制templates\cm0102-core\*.*过去。
现象5:publish后CM0102.Core.exe双击闪退,无日志

→ 原因:缺少 VC++ 运行时(Starter-Kit 的 native 依赖需要vcruntime140.dll)。
→ 解决:安装 Microsoft Visual C++ 2015-2019 Redistributable (x64) ,重启。


5. 验证运行效果:用三个终端命令确认“真·跑通”

装完不是终点,验证才是。很多人看到Build succeeded.就以为成功了,结果一跑 API 就 500。真正的“跑通”,必须同时满足:进程存活、端口监听、API 响应体正确、日志无 ERROR。下面这三行命令,是我每次部署后必敲的“后悔药”:

5.1 终端1:实时监控进程与端口(防静默退出)

# 每2秒刷新一次,同时看进程和端口 while($true) { Write-Host "=== $(Get-Date -Format 'HH:mm:ss') ===" -ForegroundColor Green; Get-Process | Where-Object {$_.ProcessName -eq "CM0102.Core"} | Select-Object Id, CPU, WS | Format-Table -AutoSize; netstat -ano | findstr :5000 | ForEach-Object { $pid = ($_ -split '\s+')[4]; "$_ -> $(Get-Process -Id $pid -ErrorAction SilentlyContinue | Select-Object -ExpandProperty ProcessName)" }; Start-Sleep -Seconds 2 }

✅ 正常输出应持续显示CM0102.Core进程 PID 和TCP 127.0.0.1:5000 0.0.0.0:0 LISTENING,且末尾附带进程名。
❌ 若某次刷新后进程消失,或端口行变成TCP [::1]:5000 [::]:0 LISTENING(IPv6 地址),说明 Kestrel 绑定失败,需回查appsettings.json。

5.2 终端2:轮询健康检查接口(验证服务层)

# 每3秒请求 /health,直到返回 200,最多试10次 $attempts = 0 do { try { $resp = Invoke-RestMethod -Uri "http://localhost:5000/health" -TimeoutSec 5 if ($resp.status -eq "Healthy") { Write-Host "✅ Health check passed at $(Get-Date)" -ForegroundColor Green break } } catch { Write-Host "⏳ Attempt $attempts: $($_.Exception.Message)" -ForegroundColor Yellow } $attempts++ Start-Sleep -Seconds 3 } while ($attempts -lt 10) if ($attempts -ge 10) { Write-Host "❌ Health check failed after 10 attempts" -ForegroundColor Red }

✅ 成功时输出绿色✅ Health check passed;
❌ 失败时会打印具体异常,如Unable to connect to the remote server(端口不通)或The operation has timed out(Kestrel 启动慢,需调大-TimeoutSec)。

5.3 终端3:抓取实时日志(验证业务逻辑)

Starter-Kit 的日志输出到logs\目录,但默认不实时刷新。我们用 PowerShell 监控最新日志文件:

# 获取最新日志文件(按修改时间排序) $logFile = Get-ChildItem "C:\cm0102-kit\logs\*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 # 实时跟踪(类似 Linux tail -f) Get-Content $logFile.FullName -Wait -Tail 10 | ForEach-Object { if ($_ -match "ERROR") { Write-Host "🚨 $_" -ForegroundColor Red } elseif ($_ -match "INFO") { Write-Host "ℹ️ $_" -ForegroundColor White } else { Write-Host $_ } }

✅ 正常日志流应持续输出INFO级别消息,如Hosting environment: Production、Now listening on: http://127.0.0.1:5000;
❌ 若出现ERROR行,如System.IO.FileNotFoundException: Could not load file or assembly 'SomeDependency.dll',说明publish阶段未正确拷贝依赖,需检查Directory.Build.props中的<CopyToPublishDirectory>设置。

从那以后我每次部署 CM0102-Starter-Kit,都强制走一遍这三个终端:一个盯进程,一个刷健康检查,一个追日志。三者全绿,才敢关掉终端去写业务代码。不是 paranoid,是这套工具链太容易在你看不见的地方悄悄失败——它不报错,只是不工作。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进