向MSBuild传参时,PropertyGroup中Substring操作失效问题
Let's break down why you're hitting this error and how to fix it.
The Root Cause: MSBuild's Evaluation Order
The key issue here is when MSBuild evaluates your properties.
Top-level <PropertyGroup> elements are processed during MSBuild's initialization phase—this happens early, before command-line properties like BUILD_VERSION are fully applied to your project. When MSBuild tries to parse this line:
<BuildMajorVersion>$([System.String]::Concat($(BuildVersion.Substring(0, $(BuildVersion.IndexOf('.')))), ".0.0.0"))</BuildMajorVersion>
At initialization time, BuildVersion (which maps to $(BUILD_VERSION)) is still an empty string. So BuildVersion.IndexOf('.') returns -1, and passing -1 as the second parameter to Substring throws the error you see: """.Substring(0,-1)" length should be inside the string.
In contrast:
- When you hardcode
BuildVersion, it has a valid value during initialization, so the string operations work as expected. - When you put the property logic inside a
<Target>'s<PropertyGroup>, MSBuild evaluates those properties during the execution phase—by this point, the command-lineBUILD_VERSIONvalue has been fully applied, soBuildVersionis correctly set to1.2.3.4, and the substring operation runs without issues.
Fixes to Try
Here are two reliable solutions to resolve this:
1. Move Version Logic to a Target (Recommended)
This is the most straightforward fix, as it leverages MSBuild's execution phase where all properties are fully resolved. Update your props file to define a target that sets the version properties before the build starts:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <IntermediateOutputPath>./obj/</IntermediateOutputPath> <BuildVersion>$(BUILD_VERSION)</BuildVersion> <InformationalVersion>$(BuildVersion)</InformationalVersion> </PropertyGroup> <Target Name="SetAssemblyVersions" BeforeTargets="CoreCompile"> <PropertyGroup> <BuildMajorVersion>$([System.String]::Concat($(BuildVersion.Substring(0, $(BuildVersion.IndexOf('.')))), ".0.0.0"))</BuildMajorVersion> <AssemblyVersion>$(BuildMajorVersion)</AssemblyVersion> <AssemblyFileVersion>$(BuildMajorVersion)</AssemblyFileVersion> </PropertyGroup> <!-- Optional: Verify the values --> <Message Text="BuildMajorVersion: $(BuildMajorVersion)" Importance="High" /> </Target> </Project>
The BeforeTargets="CoreCompile" ensures this runs right before the assembly is compiled, so the version properties are available when needed.
2. Add Guard Conditions for Empty Values
If you prefer to keep the logic in a top-level property group, you can add conditions to handle empty BuildVersion values during initialization, ensuring the substring operation only runs when the value is valid:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <IntermediateOutputPath>./obj/</IntermediateOutputPath> <BuildVersion>$(BUILD_VERSION)</BuildVersion> <InformationalVersion>$(BuildVersion)</InformationalVersion> <!-- Handle empty BuildVersion with a default, or only run substring when it's valid --> <BuildMajorVersion Condition="'$(BuildVersion)' != ''">$([System.String]::Concat($(BuildVersion.Substring(0, $(BuildVersion.IndexOf('.')))), ".0.0.0"))</BuildMajorVersion> <BuildMajorVersion Condition="'$(BuildVersion)' == ''">0.0.0.0</BuildMajorVersion> <AssemblyVersion>$(BuildMajorVersion)</AssemblyVersion> <AssemblyFileVersion>$(BuildMajorVersion)</AssemblyFileVersion> </PropertyGroup> </Project>
This way, if BuildVersion is empty during initialization, it falls back to a safe default value, avoiding the invalid substring call.
Final Notes
Always remember that MSBuild processes properties in distinct phases: initialization first, then target execution. Command-line properties and other external inputs are often only fully resolved during the execution phase, so complex property operations that depend on these inputs should usually live inside targets.
内容的提问来源于stack exchange,提问作者Ashish Negi

