Skip to content

Instantly share code, notes, and snippets.

@Wind010
Created November 5, 2025 00:20
Show Gist options
  • Select an option

  • Save Wind010/72258e8d8720397186c2f0a79aed25fe to your computer and use it in GitHub Desktop.

Select an option

Save Wind010/72258e8d8720397186c2f0a79aed25fe to your computer and use it in GitHub Desktop.
Adds all projects found under the specified repo roots to the solution. Updates the original projects package references to project references where applicable.
# Adds all projects found under the specified repo roots to the solution.
# Updates the original projects package references to project references where applicable.
# Usage: .\Convert-To-ProjectReferences.ps1 -SolutionPath "<path_to_solution>" -RepoRoots "<path_to_repo_root_1>", "<path_to_repo_root_2>"
# The IgnoreTestProjects parameter is defaulted to $true.
# The -Debug switch exists.
param(
[Parameter(Mandatory=$true)]
[string]$SolutionPath,
[Parameter(Mandatory=$true)]
[string[]]$RepoRoots,
[Parameter(Mandatory=$false)]
[string[]]$IgnoreTestProjects=$true
)
$ErrorActionPreference = "Stop"
function Get-CsprojFilesInSolution {
param($solutionPath)
Write-Host "πŸ§‘β€πŸ’» Scanning project files under '$SolutionPath' ..." -ForegroundColor Blue
$lines = Get-Content $solutionPath
$csprojPaths = $lines | Where-Object { $_ -match '\.csproj"' } | ForEach-Object {
if ($_ -match '"([^"]+\.csproj)"') { $matches[1] }
}
$solutionDir = Split-Path $solutionPath
$csprojPaths | ForEach-Object { Join-Path $solutionDir $_ }
}
function Get-ProjectMap {
param($repoRoots)
$projectMap = @{}
foreach ($root in $repoRoots) {
Write-Host "πŸ§‘β€πŸ’» Generating project map for project files under '$root' ..." -ForegroundColor Blue
Get-ChildItem -Path $root -Recurse -Filter *.csproj | ForEach-Object {
if ($IgnoreTestProjects -and $_.Directory -match 'Test|Tests') {
Write-Warning "Ignoring test project: $($_.FullName)"
return
}
[xml]$projectXml = Get-Content $_.FullName
$assemblyNameNode = $projectXml.Project.PropertyGroup.AssemblyName
if ([string]::IsNullOrWhiteSpace($assemblyNameNode)) {
Write-Warning "Project $($_.FullName) does not have an AssemblyName defined. Skipping."
}
else {
Write-Debug "Adding $assemblyNameNode to project map."
$projectMap[$assemblyNameNode] = $_.FullName
}
}
}
return $projectMap
}
function Get-RelativePath {
param($fromPath, $toPath)
Write-Host "πŸ§‘β€πŸ’» Getting relative paths from '$fromPath' to '$toPath'..." -ForegroundColor Blue
$fromUri = New-Object System.Uri($fromPath)
$toUri = New-Object System.Uri($toPath)
$relativeUri = $fromUri.MakeRelativeUri($toUri)
$relativePath = [System.Uri]::UnescapeDataString($relativeUri.ToString())
if ($toPath -like "*.csproj" -and $relativePath -notlike "*.csproj") {
$relativePath = $relativePath -replace "/", "\"
}
return $relativePath
}
function Add-ProjectsToSolution {
param(
[Parameter(Mandatory=$true)]
[string]$SolutionPath,
[Parameter(Mandatory=$true)]
[hashtable]$ProjectMap
)
foreach ($projectPath in $ProjectMap.Values) {
Write-Host "πŸ§‘β€πŸ’» Adding $projectPath to solution $SolutionPath..."
dotnet sln $SolutionPath add $projectPath
}
}
function Replace-PackageWithProjectReference {
param($csprojPath, $projectMap)
[xml]$projectXml = Get-Content $csprojPath
$changed = $false
Write-Host "`tProcessing '$csprojPath' ..."
foreach ($itemGroup in $projectXml.Project.ItemGroup) {
$packageRefs = @($itemGroup.PackageReference)
foreach ($packageRef in $packageRefs) {
$packageName = $packageRef.Include
if ([string]::IsNullOrWhitespace($packageName)) {
continue
}
if ($projectMap.ContainsKey($packageName)) {
# Remove PackageReference
Write-Debug "Replacing PackageReference '$packageName' with ProjectReference."
$itemGroup.RemoveChild($packageRef)
# Add ProjectReference with relative path
Write-Debug "Adding ProjectReference to '$($projectMap[$packageName])'."
$refPath = Get-RelativePath $csprojPath $projectMap[$packageName]
$projectRef = $projectXml.CreateElement("ProjectReference")
$projectRef.SetAttribute("Include", $refPath)
$itemGroup.AppendChild($projectRef)
$changed = $true
}
else {
Write-Debug "No matching project found for package '$packageName'."
}
}
}
if ($changed) {
$projectXml.Save($csprojPath)
Write-Host "Updated $csprojPath"
}
}
# Main
$csprojFiles = Get-CsprojFilesInSolution $SolutionPath
$projectMap = Get-ProjectMap $RepoRoots
Add-ProjectsToSolution -SolutionPath $SolutionPath -ProjectMap $projectMap
foreach ($csproj in $csprojFiles) {
Replace-PackageWithProjectReference $csproj $projectMap | Out-Null
}
Write-Host "βœ… Done! Projects in '$SolutionPath' had matching package references replaced by project references." -ForegroundColor Green
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment