powershellcsprojselect-string

PowerShell command to extract `<ProjectReference Include=(...)` from all `.csproj` files in a directory and its subdirectories


Get list of .csproj in directory and subdirectories of D:\repos\M\Test

[1] PS D:\repos\M\Test> ls -r -filter "*.csproj" | select Directory, Name

Sample output

Directory                       Name
---------                       ----
D:\repos\M\Test\M.Common        M.Common.csproj
D:\repos\M\Test\M.Mocks         M.Mocks.csproj
D:\repos\M\Test\M.StateMachines M.StateMachines.csproj

Get list of files containing line(s) that match <ProjectReference\s*Include="([^"]+)" in directory and subdirectory

[2] PS D:\repos\M\Test> ls | sls '<ProjectReference\s*Include="([^"]+)"' | ForEach-Object {New-Object PSObject -Property @{Project=$_.Filename; DirectDep=$_.Matches.Groups[1].Value}}

Sample output

Project                DirectDeps
-------                ----------
dirs.proj              D:\repos\M\Test\M.Common
dirs.proj              D:\repos\M\Test\M.Mocks.csproj
dirs.proj              D:\repos\M\Test\M.StateMachines.csproj
M.Common.csproj        D:\repos\S\Common\Common.csproj
M.Common.csproj        D:\repos\Q\Contracts\Contracts.csproj
M.Mocks.csproj         D:\repos\S\Controller\Controller.csproj
M.StateMachines.csproj D:\repos\M\Common\Common.csproj
M.StateMachines.csproj D:\repos\T\Auth\Auth.csproj

Questions

Q1: How can I feed the output of [1] to [2] to limit [2]'s search to .csproj files (i.e. exclude dirs.proj) to get the following output

Project                DirectDeps
-------                ----------
M.Common.csproj        D:\repos\S\Common\Common.csproj
M.Common.csproj        D:\repos\Q\Contracts\Contracts.csproj
M.Mocks.csproj         D:\repos\S\Controller\Controller.csproj
M.StateMachines.csproj D:\repos\M\Common\Common.csproj
M.StateMachines.csproj D:\repos\T\Auth\Auth.csproj

Q2: And then how can I take output of [2] and make it group by Project/Filename? To get the following output (or similar)

M.Common.csproj, D:\repos\S\Common\Common.csproj, D:\repos\Q\Contracts\Contracts.csproj
M.Mocks.csproj, D:\repos\S\Controller\Controller.csproj
M.StateMachines.csproj, D:\repos\M\Common\Common.csproj, D:\repos\T\Auth\Auth.csproj

Attempts

Attempts @ Q1

Attempts @ Q2

The second of the 3 has the closest output to what I'm looking for

Name                   Group
----                   -----
dirs.proj              {@{Project=dirs.proj; DirectDep=...M.Common.csproj}, {Project=dirs.proj; DirectDep=...M.Mocks.csproj}, {Project=dirs.proj; DirectDep=...M.StateMachines.csproj}
M.Common.csproj        {@{Project=M.Common.csproj; DirectDep=...Common.csproj}, {Project=M.Common.csproj; DirectDep=...Contracts.csproj}}
M.Mocks.csproj         {@{Project=M.Mocks.csproj; DirectDep=...Controller.csproj}
M.StateMachines.csproj {@{Project=M.StateMachines.csproj; DirectDep=...Common.csproj}, {Project=M.StateMachines.csproj; DirectDep=...Auth.csproj}}

Solution

  • So assuming you do have the .NET SDK installed, you can simply use dotnet list reference command to get all reference projects.

    The code would be something like:

    Get-ChildItem -Recurse -Filter *.csproj | ForEach-Object {
        $references = dotnet list $_.FullName reference
        if ($references.Count -gt 2) {
            # When there are references found the first 2 lines will be:
    
            # Project reference(s)
            # --------------------
    
            # you can choose to skip them here if you want to or just remove this condition
            $references = $references | Select-Object -Skip 2
        }
    
        [pscustomobject]@{
            ProjectName       = $_.BaseName
            ProjectPath       = $_.FullName
            PackageReferences = $references
        }
    }
    

    As for the previous question, "how to exclude for example, dirs.proj", if you use -Filter *.csproj it shouldn't list it at all but for the sake of answering, you can use -Exclude:

    Get-ChildItem -Recurse -Filter *.csproj -Exclude dirs.proj |
        ForEach-Object { .... } # same code shown above
    

    Or Where-Object:

    Get-ChildItem -Recurse -Filter *.csproj |
        Where-Object Name -NE dirs.proj |
        ForEach-Object { .... } # same code shown above