Netscoot.Core/Public/Get-SolutionInventory.ps1
|
function Get-SolutionInventory { <# .SYNOPSIS List the full contents of every solution in a repository (projects of any type, solution folders, and solution items), plus on-disk projects that no solution references. .DESCRIPTION Where Test-SolutionConsistency compares membership and Repair-SolutionReferences finds dangling entries, this gives the complete picture without reading the files by hand. It parses each .sln/.slnx directly (not via `dotnet sln list`, which only returns CLI-buildable projects), so it also surfaces non-CLI project types (e.g. .pssproj), solution folders, and loose solution items. It then compares against the projects on disk and flags any that are in no solution at all. Read-only: One record per item, so you can group, filter, or format it however you like. .PARAMETER RepositoryRoot Root to scan. Accepts pipeline input: a path string, or a file/directory item from Get-Item / Get-ChildItem. Defaults to the enclosing git repository root. Nested git worktrees are skipped. .OUTPUTS Netscoot.SolutionItem - one per item. .EXAMPLE # Everything across all solutions, plus projects in none Get-SolutionInventory -RepositoryRoot . | Format-Table -AutoSize # Only the projects on disk that no solution references Get-SolutionInventory | Where-Object Kind -eq 'UnreferencedProject' # Only loose solution items (e.g. a README in a solution folder) Get-SolutionInventory | Where-Object Kind -eq 'SolutionItem' # Kind is the [Netscoot.SolutionItemKind] enum, so this also works Get-SolutionInventory | Where-Object Kind -eq ([Netscoot.SolutionItemKind]::UnreferencedProject) .LINK Test-SolutionConsistency .LINK Sync-Solution .LINK Repair-SolutionReferences #> [CmdletBinding()] [OutputType('Netscoot.SolutionItem')] param( [Parameter(Position = 0, ValueFromPipeline)] [Netscoot.PathInputTransform()] [string]$RepositoryRoot ) process { if (-not $RepositoryRoot) { $RepositoryRoot = Get-RepositoryRoot -StartPath (Get-Location).Path } $RepositoryRoot = Resolve-FullPath $RepositoryRoot function _rel([string]$p) { (Get-RelativePathSafe -From $RepositoryRoot -To $p) } # One builder for every row, so the Netscoot.SolutionItem shape (property set and order) is # defined in a single place rather than repeated at each call site. function _item([string]$Solution, [Netscoot.SolutionItemKind]$Kind, [string]$Name, [string]$Path = '', [string]$Type = '') { [pscustomobject]@{ PSTypeName = 'Netscoot.SolutionItem' Solution = $Solution Kind = $Kind Type = $Type Name = $Name Path = $Path } } # One repository parse for this invocation: solutions (each already parsed via Read-Solution) # and the project glob both come from the workspace, so no file is read twice. $workspace = Get-Workspace -RepositoryRoot $RepositoryRoot $solutions = @(Get-WorkspaceSolutions -Workspace $workspace) $seen = [System.Collections.Generic.List[string]]::new() foreach ($sln in $solutions) { $rel = _rel $sln.FullName # The workspace solution entry IS the parsed Netscoot.Solution, so its file contents # (projects of any type, folders, items) are already on it - no second Get-SolutionContent. $content = $sln foreach ($p in $content.Projects) { $seen.Add($p.Abs) _item -Solution $rel -Kind ([Netscoot.SolutionItemKind]::Project) ` -Name (Split-Path -Leaf $p.Abs) -Path $p.Stored -Type $p.Ext.TrimStart('.') } foreach ($f in $content.Folders) { _item -Solution $rel -Kind ([Netscoot.SolutionItemKind]::SolutionFolder) -Name $f } foreach ($i in $content.Items) { _item -Solution $rel -Kind ([Netscoot.SolutionItemKind]::SolutionItem) -Name (Split-Path -Leaf $i) -Path $i } } # Projects on disk (managed and native) that no solution references at all. foreach ($disk in (Get-WorkspaceProjectFiles -Workspace $workspace -IncludeNative)) { $abs = Resolve-FullPath $disk.FullName if (-not (Test-PathInList -Path $abs -List $seen)) { _item -Solution '(none)' -Kind ([Netscoot.SolutionItemKind]::UnreferencedProject) ` -Name $disk.Name -Path (_rel $abs) -Type $disk.Extension.TrimStart('.') } } } } |