How do I programmatically list all of the projects in a solution? I'll take a script, command-line, or API calls.
14 Answers
Here's a PowerShell script that retrieves project details from a .sln file:
Get-Content 'Foo.sln' |
Select-String 'Project\(' |
ForEach-Object {
$projectParts = $_ -Split '[,=]' | ForEach-Object { $_.Trim('[ "{}]') };
New-Object PSObject -Property @{
Name = $projectParts[1];
File = $projectParts[2];
Guid = $projectParts[3]
}
}

- 10,735
- 1
- 31
- 44
-
3I know this is old, but it is beautiful. Saved me a bunch of time. – ncooper09 Mar 20 '17 at 15:14
-
5If you need to, you can filter out solution folders by adding an additional Select-String between lines 2 and 3 as follows: `Select-String "{2150E333-8FDC-42A3-9474-1A3956D46DE8}" -NotMatch |` – alastairs Apr 26 '17 at 21:52
var Content = File.ReadAllText(SlnPath);
Regex projReg = new Regex(
"Project\\(\"\\{[\\w-]*\\}\"\\) = \"([\\w _]*.*)\", \"(.*\\.(cs|vcx|vb)proj)\""
, RegexOptions.Compiled);
var matches = projReg.Matches(Content).Cast<Match>();
var Projects = matches.Select(x => x.Groups[2].Value).ToList();
for (int i = 0; i < Projects.Count; ++i)
{
if (!Path.IsPathRooted(Projects[i]))
Projects[i] = Path.Combine(Path.GetDirectoryName(SlnPath),
Projects[i]);
Projects[i] = Path.GetFullPath(Projects[i]);
}
Edit: Amended the regex to include the ".*" as per the comment by Kumar Vaibhav

- 4,429
- 3
- 39
- 57

- 6,487
- 8
- 50
- 64
-
6The above works but there is a little problem. If your projects are named like "AB.CD" - I mean when the '.' is there then the regex would not recognize those. The following little change would make it work - Regex projReg = new Regex( "Project\\(\"\\{[\\w-]*\\}\"\\) = \"([\\w _]*.*)\", \"(.*\\.(cs|vcx|vb)proj)\"" , RegexOptions.Compiled); – Kumar Vaibhav Jul 10 '13 at 10:27
-
Can anyone explain why you need `IsPathRooted` here? Not sure why a project would ever be rooted, because I've never seen such a solution file. – Bartleby Nov 07 '22 at 19:04
The trick is to choose the right MsBuild.dll. Under VS2017 it is indeed "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\amd64\Microsoft.Build.dll" (Dont use the standard Msbuild ddl in references. Browse to this path)
c#:
var solutionFile =
SolutionFile.Parse(@"c:\NuGetApp1\NuGetApp1.sln");//your solution full path name
var projectsInSolution = solutionFile.ProjectsInOrder;
foreach(var project in projectsInSolution)
{
switch (project.ProjectType)
{
case SolutionProjectType.KnownToBeMSBuildFormat:
{
break;
}
case SolutionProjectType.SolutionFolder:
{
break;
}
}
}
powershell:
Add-Type -Path (${env:ProgramFiles(x86)} + '\Microsoft Visual
Studio\2017\Professional\MSBuild\15.0\Bin\amd64\Microsoft.Build.dll')
$slnPath = 'c:\NuGetApp1\NuGetApp1.sln'
$slnFile = [Microsoft.Build.Construction.SolutionFile]::Parse($slnPath)
$pjcts = $slnFile.ProjectsInOrder
foreach ($item in $pjcts)
{
switch($item.ProjectType)
{
'KnownToBeMSBuildFormat'{Write-Host Project : $item.ProjectName}
'SolutionFolder'{Write-Host Solution Folder : $item.ProjectName}
}
}

- 1,003
- 10
- 4
You can use the EnvDTE.Solution.Projects object to programmatically get access to the projects in a solution.
One gotcha though is that if you have any SolutionFolders in your solution, any projects in these folders are not shown in the above collection.
I've written an article including a code sample on how to get all projects regardless of any solutionfolders

- 80
- 1
- 8

- 1,194
- 10
- 34
-
3This can only be used in a VS extension, so it useless for anybody who wants a stand alone tool for reading source projects in a solution. – Alex Aug 09 '16 at 09:27
-
You can get a reference to a [running instance of VS](https://github.com/wwwlicious/VSAutomate/blob/master/VSAutomate/Ide.cs#L25) or you can access `$dte` from the package manager console. If you dont have VS running at all then you need an alternative solution. The new MSBuild packages now contain classes for parsing solution and project files but these are still pre-RTM – Scott Mackay Aug 09 '16 at 12:23
-
updated link: https://wwwlicious.com/envdte-getting-all-projects-html/ – PatrickR Aug 29 '19 at 18:57
Currently you can use Package Manager Console in VS to obtain that info. Use powershell Get-Project
command
Get-Project -All

- 10,211
- 6
- 43
- 75

- 1,078
- 11
- 21
just read the list from *.sln file. There are "Project"-"EndProject" sections.
Here is an article from MSDN.

- 13,035
- 13
- 56
- 62
-
-
not any **Parser C# class** for read _sln files_ ? Similar like ***http://stackoverflow.com/questions/707107/parsing-visual-studio-solution-files*** **http://stackoverflow.com/questions/1243022/parsing-tnsnames-ora-in-visual-c-sharp-2008** – Kiquenet Oct 02 '15 at 15:05
There's a really elegant solution here: Parsing Visual Studio Solution files
The answer by John Leidegren involves wrapping the internal Microsoft.Build.Construction.SolutionParser
class.

- 15,374
- 13
- 103
- 121

- 351
- 1
- 15
Since Visual Studio 2013 the Microsoft.Build.dll provides a SolutionFile object with some very handy functions.
Here's an example of using the v14.0 version to list the relative path of all the projects in the order they appear in the solution.
Add-Type -Path (${env:ProgramFiles(x86)} + '\Reference Assemblies\Microsoft\MSBuild\v14.0\Microsoft.Build.dll')
$solutionFile = '<FULL PATH TO SOLUTION FILE>'
$solution = [Microsoft.Build.Construction.SolutionFile] $solutionFile
($solution.ProjectsInOrder | Where-Object {$_.ProjectType -eq 'KnownToBeMSBuildFormat'}).RelativePath
There are plenty of other properties on the project object (ProjectName, AbsolutePath, configurations etc) that may be of use. In the above example I used the ProjectType to filter out Solution Folders.

- 377
- 3
- 9
-
For VS2017, it is under VS2017 ```"C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\amd64\Microsoft.Build.dll" ``` And annoying, but for sqlprojs files, project type is Unknown ```[SolutionProjectType]::Unknown``` – demokritos Aug 14 '17 at 09:37
I know that this is maybe already answered question, but I would like to share my approach of reading sln file. Also during run time I am determining if project is Test project or not
function ReadSolutionFile($solutionName)
{
$startTime = (Get-Date).Millisecond
Write-Host "---------------Read Start---------------"
$solutionProjects = @()
dotnet sln "$solutionName.sln" list | ForEach-Object{
if($_ -Match ".csproj" )
{
#$projData = ($projectString -split '\\')
$proj = New-Object PSObject -Property @{
Project = [string]$_;
IsTestProject = If ([string]$_ -Match "test") {$True} Else {$False}
}
$solutionProjects += $proj
}
}
Write-Host "---------------Read finish---------------"
$solutionProjects
$finishTime = (Get-Date).Millisecond
Write-Host "Script run time: $($finishTime-$startTime) mil"
}
Hope this will be helpfull.

- 1,210
- 16
- 11
If you need to do this on a non Windows machine, you can use the following Bash command:
grep "Project(" NameOfYourSolution.sln | cut -d'"' -f4

- 12,070
- 18
- 71
- 107
To expand on the answer by @brianpeiris:
Function Global:Get-ProjectInSolution {
[CmdletBinding()] param (
[Parameter()][string]$Solution
)
$SolutionPath = Join-Path (Get-Location) $Solution
$SolutionFile = Get-Item $SolutionPath
$SolutionFolder = $SolutionFile.Directory.FullName
Get-Content $Solution |
Select-String 'Project\(' |
ForEach-Object {
$projectParts = $_ -Split '[,=]' | ForEach-Object { $_.Trim('[ "{}]') }
[PSCustomObject]@{
File = $projectParts[2]
Guid = $projectParts[3]
Name = $projectParts[1]
}
} |
Where-Object File -match "csproj$" |
ForEach-Object {
Add-Member -InputObject $_ -NotePropertyName FullName -NotePropertyValue (Join-Path $SolutionFolder $_.File) -PassThru
}
}
This filters to only .csproj
files and adds the full path of each based on the File
field and the path containing the sln
file.
Use Get-ProjectInSolution MySolution.sln | Select-Object FullName
to get each of the full file paths.
The reason I wanted the full path was to be able to access the packages.config
files beside each project file and then get the packages from all of them:
Get-ProjectInSolution MySolution.sln |
%{Join-Path ($_.FullName | Split-Path) packages.config} |
%{select-xml "//package[@id]" $_ | %{$_.Node.GetAttribute("id")}} |
select -unique

- 1,106
- 1
- 16
- 32
Here is a modified approach I used to look at the problem from a Solution-first stance:
Function Get-ProjectReferences ($rootFolder) {
$ns = @{ defaultNamespace = "http://schemas.microsoft.com/developer/msbuild/2003" }
$solutionFilesWithContent = Get-ChildItem $rootFolder -Filter *.sln -Recurse |
ForEach-Object {
New-Object PSObject -Property @{
SolutionFile = $_;
SolutionContent = Get-Content $_;
}
}
$projectFilesWithContent = Get-ChildItem $rootFolder -Filter *.csproj -Recurse |
ForEach-Object {
New-Object PSObject -Property @{
ProjectFile = $_;
ProjectContent = [xml](Get-Content $_);
}
}
$solutionFilesWithContent | ForEach-Object {
$solutionFileWithContent = $_
$projectsInSolutionStrings = $solutionFileWithContent.SolutionContent | Select-String 'Project\('
$projectsInSolution = $projectsInSolutionStrings |
ForEach-Object {
$projectParts = $_ -Split '[,=]' | ForEach-Object { $_.Trim('[ "{}]') };
if ($projectParts[2].Contains(".csproj")) {
New-Object PSObject -Property @{
Name = $projectParts[1];
File = $projectParts[2];
Guid = $projectParts[3];
}
}
}
$projectsInSolution | ForEach-Object {
$projectFileSearchName = $_.Name + ".csproj"
$projectInSolutionFile = $projectFilesWithContent | Where-Object { $_.ProjectFile.Name -eq $projectFileSearchName } | Select-Object -First 1
if (($null -eq $projectInSolutionFile) -and ($null -eq $projectInSolutionFile.ProjectContent)) {
Write-Host "Project was null"
}
else {
$projectInSolutionName = $projectInSolutionFile.ProjectFile | Select-Object -ExpandProperty BaseName
$projectReferences = $projectInSolutionFile.ProjectContent | Select-Xml '//defaultNamespace:ProjectReference/defaultNamespace:Name' -Namespace $ns | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty "#text"
$projectTarget = $projectInSolutionFile.ProjectContent | Select-Xml "//defaultNamespace:TargetFrameworkVersion" -Namespace $ns | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty "#text"
$projectReferences | ForEach-Object {
$projectFileName = $_ + ".csproj"
$referenceProjectFile = $projectFilesWithContent | Where-Object { $_.ProjectFile.Name -eq $projectFileName } | Select-Object -First 1
if ($null -eq $referenceProjectFile) {
$referenceProjectTarget = "Unknown"
}
else {
$referenceProjectTarget = $referenceProjectFile.ProjectContent | Select-Xml "//defaultNamespace:TargetFrameworkVersion" -Namespace $ns | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty "#text"
}
"[" + $solutionFileWithContent.SolutionFile.Name + "] -> [" + $projectInSolutionName + " " + $projectTarget + "] -> [" + $_ + " " + $referenceProjectTarget + "]"
}
}
}
}
}
Get-ProjectReferences "C:\src\repos\MyRepo" | Out-File "C:\src\repos\FrameworkDependencyAnalysis\FrameworkDependencyAnalysis.txt"

- 472
- 5
- 17
-
-
In this case I was focused on .NET Framework, because my department has a large inventory of .NET Framework projects still targeting 4.5.* and we're starting an effort to move them to 4.8. – Thomas Parikka Mar 18 '22 at 15:57