How to list all target frameworks of all projects for a given solution inside Visual Studio?
Asked Answered
M

3

14

Given an opened solution in Visual Studio, how do I quickly check which target frameworks the various projects in the solution have? Is there a solution-wide view somewhere that shows which target framework each project targets, or an aggregate view of how many projects target each framework version?

I'm aware I can check each project individually (either on properties window or on the csproj file itself), however in a solution with 100+ projects this is not feasible.

Additionally, I know I could probably do some sort of regex search inside csproj files in the root folder, but I was wondering if there was something built-in in Visual Studio to provide this data.

Mendicity answered 8/3, 2021 at 14:57 Comment(1)
You could start a bounty to get the answer.Chaffinch
H
4

You could get MSBuild to print this out for you.

Add a Directory.Build.targets file at the top level of your code that prints out the TargetFramework value.

This does the trick for me:

<Project>

  <Target Name="LogTargetFramework" AfterTargets="CoreCompile">
    <Message
      Importance="high"
      Text="Project $(MSBuildProjectName): $(TargetFramework)"/>
  </Target>

</Project>

Adding that to, for example, the MetadataExtractor solution and rebuilding it produces:

1>Project MetadataExtractor: net35
1>Project MetadataExtractor: net45
1>Project MetadataExtractor: netstandard2.0
1>Project MetadataExtractor: netstandard1.3
3>Project MetadataExtractor.PowerShell: net40
2>Project MetadataExtractor.Samples: net48
5>Project MetadataExtractor.Tools.JpegSegmentExtractor: net6.0
4>Project MetadataExtractor.Benchmarks: net461
7>Project MetadataExtractor.Tests: net472
6>Project MetadataExtractor.Tools.FileProcessor: net6.0
7>Project MetadataExtractor.Tests: net6.0

Using MSBuild to get this data means you'll get correct results. Parsing XML is no substitute for actually running the build, as property values can be overridden in any number of ways.

Hodges answered 27/1, 2023 at 11:22 Comment(4)
This is a fairly simple solution, so I quite like it! Those emitted messages however... won't they be hidden by a sea of other build messages from the project? Or are you recommending just building with minimal logs so they stand out like a report?Mendicity
And another question: are you sure that every project will always end up executing this target? It depends on CoreCompile being executed. Does that always happen even for incremental builds no matter the project type?Mendicity
You need to rebuild for this to work. VS will skip calling MSBuild altogether if it thinks the project is up-to-date. If you rebuild, it's fine. And yes, those messages get interspersed with other messages. I assume this is something you'd only want to do occasionally, so perhaps you could prefix the message with some unique string (like ****) to make it easy to filter them out (manually or with command line tooling).Hodges
Not all projects have a <TargetFramework> element. Some have only <TargetFrameworkVersion>, some have both. So I used Text="...: $(TargetFramework) | $(TargetFrameworkVersion)". Not terribly elegant, but it got the job done.Santee
S
2

I couldn't find anything so decided to write a script instead:

# Set root folder to current script location
$rootFolder = $PSScriptRoot
$solutionName = '[YOUR_SOLUTION_PATH]'

# Define the path to the solution file
$solutionFile = Join-Path $rootFolder $solutionName

# Read the contents of the solution file
$solutionText = Get-Content $solutionFile

# Use a regular expression to extract the names of the project files
$projectFiles = [regex]::Matches($solutionText, 'Project\("{([A-Za-z0-9-]+)}"\) = "([^"]+)", "([^"]+.csproj)"') | ForEach-Object { $_.Groups[3].Value } | Sort-Object

# Define project collection
$projects = @()

# Iterate over each project file
foreach ($projectFile in $projectFiles) {

    # Read the contents of the project file
    $projectText = Get-Content (Join-Path $rootFolder $projectFile)

    # Determine whether it is a SDK style project
    $isSdkProject = [regex]::IsMatch($projectText, '<Project Sdk="Microsoft.NET.Sdk">')

    # Use a regular expression to extract the target framework
    $targetFramework = [regex]::Match($projectText, '<TargetFramework>(.+)</TargetFramework>')
    
    # Get the target framework
    $foundFramework = if ($targetFramework.Success) { $($targetFramework.Groups[1].Value) } else { 'None' }

    # Add to projects collection
    $projects += [pscustomobject]@{ Project=$projectFile; SdkFormat=$isSdkProject; TargetFramework=$foundFramework; }
}

# Output projects as table
$projects | Format-Table

# Display summary
Write-Host $projects.Count "projects found"

It lists all projects, their target framework, and whether they are SDK style.

Selfconfidence answered 11/1, 2023 at 15:0 Comment(5)
While this might work, I wouldn't recommend the approach. Instead, if you really want to use a script or an app to do it, I'd suggest using proper MSBuild project reading packages and interpreting the values using their native types. That would be a lot more robust than just trying to hack at the data manually IMHO. Having said that, this doesn't solve the requirement as I originally asked, so I won't be marking it as an answer.Mendicity
Like you say it works. Appreciate it may not answer your question specifically. In my case I needed a quick overview of a 300 project solution to aid a migration to .net core. This was a simple solution.Selfconfidence
To be clear, I'm not dissing your answer (I didn't downvote it, for example). It's just that I was looking for a more permanent, and built-in solution with this question in particular. When I said I wouldn't recommend your approach here I meant as a long-term solution: for example, someone took your code and created a Visual Studio extension with it, or even if you were creating this as a long term script in a real production system that is going to be used by multiple devs etc. My point was specifically on overall robustness and future-proofness of the solution.Mendicity
@BrettPostin I tried this, it only output a list of projects. Has anything changed since you wrote your answer?Kessia
@HarryAdams Not sure, though i see the same on command line. Running in ISE produces all the expected columnsSelfconfidence
W
0

Not built-in, and not sure if robust. But it works for me and is available at a right-click. Suggest improvements if you have.

Save this powershell script in a file like 'Get_TargetFrameworks.ps1':

param (
    [string]$solutionFolder
)
$indentColumn = 30  

Get-ChildItem $solutionFolder -Include *.csproj -Recurse -Force | ForEach-Object {
    [xml]$projectXml = (Get-Content ($_))
    $namespace=New-Object System.Xml.XmlNamespaceManager($projectXml.NameTable)
    $namespace.AddNamespace("nsp", $projectXml.DocumentElement.NamespaceURI)

    $dotnetVersionNode = $projectXml.SelectSingleNode("//nsp:TargetFrameworkVersion", $namespace)
    if ($dotnetVersionNode -eq $null) {
        $dotnetVersionNode = $projectXml.SelectSingleNode("//nsp:TargetFramework", $namespace)
    }

    $indent = $_.Name.Replace(".csproj", "") + " "
    $spacesNeeded = [Math]::Max(0, $indentColumn - $indent.Length)
    $spaces = ' ' * $spacesNeeded
    $output = "{0}{1}{2}" -f $indent, $spaces, $dotnetVersionNode.InnerXml
    Write-Host $output
}
 
Pause

Add to Explorer right-click menu:

Swap <File path> with actual, save as 'Get_TargetFrameworks.reg' file, and run to install in registry:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\GetTargetFrameworks]
@="Get Target Frameworks"

[HKEY_CLASSES_ROOT\Directory\Background\shell\GetTargetFrameworks\command]
@="PowerShell.exe -ExecutionPolicy Bypass -File \"<File path>""
Witting answered 11/7 at 19:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.