Skip to content

Instantly share code, notes, and snippets.

@nimatt
Last active May 2, 2022 08:32
Show Gist options
  • Save nimatt/1c68f0735c02701a411f2fb921fa69cc to your computer and use it in GitHub Desktop.
Save nimatt/1c68f0735c02701a411f2fb921fa69cc to your computer and use it in GitHub Desktop.
param (
[IO.FileInfo]$solution
)
$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
[IO.FileInfo]$solution = (Resolve-Path -Path $solution).Path
Push-Location $solution.Directory
if (-not $solution.Exists) {
throw 'Solution does not exist'
}
function Get-Projects {
param (
[IO.FileInfo]$solution
)
$lines = Get-Content -Encoding Unicode $solution
$projects = @{}
[Text.RegularExpressions.Regex]::Matches($lines, 'Project\("(?<type>{[A-F\-0-9]+})"\)\s*=\s*"(?<name>[^"]+)"\s*,\s*"(?<path>[^"]+)"\s*,\s*"(?<id>{[A-F\-0-9]+})"') |
ForEach-Object {
$projects.Add(
$_.Groups['id'].Value,
(New-Object PSObject -Property @{
Id = $_.Groups['id'].Value
Name = $_.Groups['name'].Value
Path = $_.Groups['path'].Value
Type = $_.Groups['type'].Value
})
)
}
return $projects
}
function Get-Relations {
param (
[IO.FileInfo]$solution
)
$lines = Get-Content -Encoding Unicode $solution
$relations = @{}
[Text.RegularExpressions.Regex]::Match([string]::Join("`n", $lines), "GlobalSection\(NestedProjects\)[^G]+EndGlobalSection") |
ForEach-Object { [Text.RegularExpressions.Regex]::Matches($_, '(?<id>{[A-F\-0-9]+})\s*=\s*(?<parent>{[A-F\-0-9]+})') } |
ForEach-Object { $relations.Add($_.Groups['id'].Value, $_.Groups['parent'].Value) } | Out-Null
return $relations
}
function Get-Path($id, $relations, $projects) {
if (-not $relations.ContainsKey($id)) {
return $projects[$id].Name
}
return (Get-Path $relations[$id] $relations $projects) + '\' + $projects[$id].Name
}
function Get-RelativePath {
param (
[string]$from,
[string]$to
)
if ($to.StartsWith($from)) {
return $to.Substring($from.Length + 1)
}
return [IO.Path]::Combine(
'..',
(Get-RelativePath $from.SubString(0, $from.LastIndexOf([IO.Path]::DirectorySeparatorChar)) $to)
)
}
function Get-ProjectMappings {
param(
$projects,
$relations
)
$mappings = $projects.Keys |
Where-Object { $projects[$_].Name -ne $projects[$_].Path} |
ForEach-Object {
$vsPath = Get-Path $_ $relations $projects
$projFile = [IO.FileInfo][IO.Path]::Combine($solution.Directory.FullName, $projects[$_].Path)
$newFilePath = [IO.FileInfo][IO.Path]::Combine($solution.Directory.FullName, $vsPath, $projFile.Name)
New-Object PSObject -Property @{
Name = $projects[$_].Name
Id = $_
VsPath = $vsPath
NewFilePath = $newFilePath.FullName
ProjFile = $projFile
}
}
return $mappings
}
function Get-ProjectFiles {
param (
$projects
)
$projFiles = ($projects.Values).Path |
ForEach-Object { [IO.FileInfo][IO.Path]::Combine($solution.Directory.FullName, $_) } |
Where-Object { $_.Exists }
return $projFiles
}
function Fix-References {
param (
$projFiles,
$projFileMap
)
$projFiles |
ForEach-Object {
$projFile = $_
$newProjPath = $projFileMap[$_.FullName].NewFilePath
$projContent = Get-Content -Raw $_
$references = [Text.RegularExpressions.Regex]::Matches($projContent, '<ProjectReference\s+Include="(?<path>[^"]+)" />') |
ForEach-Object {
$current = $_.Groups['path'].Value
$fullPath = [IO.Path]::GetFullPath([IO.Path]::Combine($projFile.Directory.FullName, $current))
New-Object PSObject -Property @{
Current = $current
FullPath = $fullPath
New = if ($projFileMap.ContainsKey($fullPath)) { Get-RelativePath ([IO.FileInfo]$newProjPath).Directory.FullName $projFileMap[$fullPath].NewFilePath } else { $current }
}
}
$newProjContent = $projContent
$references |
ForEach-Object {
$newProjContent = $newProjContent.Replace($_.Current, $_.New)
}
[System.IO.File]::WriteAllLines($projFile.FullName, $newProjContent)
}
}
function Fix-SolutionFile {
param (
$solution,
$faulty
)
$solutionContent = Get-Content -Raw -Path $solution.FullName
$faulty |
ForEach-Object {
$oldPath = Get-RelativePath $solution.Directory $_.ProjFile
$newPath = Get-RelativePath $solution.Directory $_.NewFilePath
$solutionContent = [Text.RegularExpressions.Regex]::Replace($solutionContent, "\s*`"$($oldPath.Replace('\', '\\'))`"\s*,\s*`"$($_.Id)`"", " `"$newPath`", `"$($_.Id)`"")
}
[System.IO.File]::WriteAllLines($solution.FullName, $solutionContent)
}
function Move-Projects {
param (
$solution,
$faulty
)
$faulty |
ForEach-Object {
$newDir = ([IO.FileInfo]$_.NewFilePath).Directory
if (-not $newDir.Exists) {
$newDir.Create()
}
Write-Host $_.ProjFile.Directory.FullName + ' -> ' + $newDir.FullName.TrimEnd('\')
Move-Item "$($_.ProjFile.Directory.FullName)\*" $newDir
$_.ProjFile.Directory.Delete()
}
}
function Delete-EmptyFolders {
param (
$solution
)
Get-ChildItem $solution.Directory.FullName -Recurse -Directory |
Where-Object { (Get-ChildItem $_.FullName) -eq $null } |
ForEach-Object { $_.Delete() }
}
try {
$projects = Get-Projects $solution
$relations = Get-Relations $solution
$mappings = Get-ProjectMappings $projects $relations
$faulty = $mappings | Where-Object {$_.VsPath -ne $_.ProjFile.Directory.FullName.SubString($solution.Directory.FullName.Length + 1)} | Sort-Object -Property VsPath
$projFileMap = @{}
$mappings | ForEach-Object { $projFileMap.Add($_.ProjFile.FullName, $_) }
$projFiles = Get-ProjectFiles $projects
Fix-References $projFiles $projFileMap
Fix-SolutionFile $solution $faulty
Move-Projects $solution $faulty
Delete-EmptyFolders $solution
} finally {
Pop-Location
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment