microsoft/modifying-project-properties
> Modifies .NET project properties in PropertyGroup elements within .csproj, .vbproj, and Directory.Build.props files. Handles TargetFramework, LangVersion, Nullable, OutputType, TreatWarningsAsErrors, and other MSBuild configuration settings. Use when asked to "change TargetFramework", "update project settings", "enable nullable", "set LangVersion", or modify any build property. Also triggers for Directory.Build.props changes, conditional PropertyGroup handling, and centralized vs project-specific property decisions.
npx skills add https://github.com/microsoft/upgrade-agent-plugins --skill modifying-project-properties
Modify .NET project properties in <PropertyGroup> elements. Properties may be defined across multiple files in the MSBuild import chain, so always discover where a property is defined before modifying it — changing the wrong file creates confusing overrides or has no effect.
Common properties: TargetFramework, LangVersion, Nullable, OutputType, AssemblyName, TreatWarningsAsErrors, ImplicitUsings
Properties can be defined in multiple locations, evaluated in this order:
SDK imports → Directory.Build.props → Project file → Directory.Build.targets → SDK imports
| Location | Purpose |
|---|---|
| Directory.Build.props | Centralized defaults for all projects |
| .csproj/.vbproj | Project-specific settings and overrides |
| Directory.Build.targets | Post-evaluation defaults |
| Explicit <Import> | Custom shared configuration |
Later definitions override earlier ones, which is why project-file properties override Directory.Build.props values.
get_project_dependencies <path-to-csproj>
This reveals which .props/.targets files are imported and where packages are referenced. Always run this first — properties are often not where you expect.
Supplement with direct file inspection:
# Find all props files
find . -name "Directory.Build.props"
# Search for a specific property across all project files
grep -r "TargetFramework" . --include="*.csproj" --include="*.vbproj" --include="*.fsproj" --include="*.props"
Search each file in the import chain for the target property. Pay attention to Condition attributes — they limit when a property applies:
<!-- This only applies in Debug builds -->
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<Optimize>false</Optimize>
</PropertyGroup>
| Scenario | Action | Why |
|---|---|---|
| Property in Directory.Build.props | Modify there | Respect centralization; changing elsewhere creates a confusing override |
| Property in project file | Modify project file | It's an intentional override |
| Property doesn't exist | Ask user | User decides if it should be centralized or project-specific |
Centralization guidance:
Use str_replace on the correct file.
When modifying conditional properties:
Tell the user which file was modified and the blast radius:
✅ Updated TargetFramework to net8.0 in Directory.Build.props
⚠️ This affects ALL projects in the repository.
Task Progress:
- [ ] Step 1: Discover import chain with get_project_dependencies
- [ ] Step 2: Find existing property definition(s)
- [ ] Step 3: Determine correct modification location
- [ ] Step 4: Apply the change
- [ ] Step 5: Report impact to user
Task: Update TargetFramework to net8.0
get_project_dependencies → find Directory.Build.props in import chain<TargetFramework>net48</TargetFramework> in Directory.Build.props str_replace Directory.Build.props
old: <TargetFramework>net48</TargetFramework>
new: <TargetFramework>net8.0</TargetFramework>
Task: Set LangVersion to 12.0 for ExperimentalProject.csproj
<LangVersion>11.0</LangVersion> in Directory.Build.props<LangVersion>preview</LangVersion> override in ExperimentalProject.csproj str_replace ExperimentalProject.csproj
old: <LangVersion>preview</LangVersion>
new: <LangVersion>12.0</LangVersion>
Task: Enable nullable reference types
str_replace Directory.Build.props
old: <LangVersion>11.0</LangVersion>
</PropertyGroup>
new: <LangVersion>11.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
Task: Set Optimize=true unconditionally (currently conditional)
Found in Directory.Build.props:
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<Optimize>false</Optimize>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<Optimize>true</Optimize>
<DebugType>portable</DebugType>
</PropertyGroup>
Option A — Extract to unconditional group (preserves other conditional properties):
<PropertyGroup>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DebugType>portable</DebugType>
</PropertyGroup>
Option B — Update within conditions (simpler, keeps structure):
Change <Optimize>false</Optimize> to <Optimize>true</Optimize> in Debug group only.
| Category | Properties |
|---|---|
| Framework/Language | TargetFramework, LangVersion, ImplicitUsings |
| Code Quality | Nullable, TreatWarningsAsErrors, WarningLevel, NoWarn |
| Output | OutputType (Exe/Library/WinExe), AssemblyName, RootNamespace |
| Build | Deterministic, DebugType, Optimize |
Watch for: Multiple Directory.Build.props files (nearest to project wins), intentional overrides in project files, Condition attributes on PropertyGroup or individual properties.
| Problem | Solution |
|---|---|
| Property not taking effect | Check for Condition attributes limiting when it applies; check for overrides in project file |
| Changed Directory.Build.props but one project unaffected | Project has explicit override in the project file; check for excluding Condition |
| Unexpected property value | Run dotnet msbuild /pp to see fully evaluated project with all imports resolved |
| Multiple Directory.Build.props — which wins? | Nearest to project file: repo root → src/ → src/MyApp/ |
For package management (ItemGroup elements), see the project-package-management skill.
Take microsoft/modifying-project-properties from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.