functions/sln-verification.ps1
import-module pathutils import-module publishmap function get-slndependencies { [CmdletBinding(DefaultParameterSetName = "sln")] param( [Parameter(Mandatory=$true, ParameterSetName="sln",Position=0)][Sln]$sln, [Parameter(Mandatory=$true, ParameterSetName="slnfile",Position=0)][string]$slnfile ) if ($sln -eq $null) { $sln = import-sln $slnfile } $projects = get-slnprojects $sln | ? { $_.type -eq "csproj" } $deps = $projects | % { if (test-path $_.fullname) { $p = import-csproj $_.fullname $refs = @() $refs += @($p | get-projectreferences) $refs += @($p | get-nugetreferences) } else { $p = $null $refs = $null } return new-object -type pscustomobject -property @{ project = $_; csproj = $p; refs = $refs } } $result = @() foreach($p in $deps) { if ($p.refs -ne $null -and $p.refs.length -gt 0) { foreach($r in $p.refs) { $path = $r.path $path = join-path (split-path -parent $p.project.fullname) $r.path $slnrel = get-relativepath (split-path -parent $sln.fullname) $path $slnproj = $projects | ? { $_.path -eq $slnrel } $existsInSln = $slnproj -ne $null $exists = test-path $path #$null = $r | add-property -name "Valid" -value $existsInSln if ($r.type -eq "project") { $r.IsValid = $r.IsValid -and $existsInSln } $version = $null if ($r.type -eq "nuget") { if ($r.path -ne $null -and $r.path.replace("\","/") -match "/.*?(?<version>[0-9]+\.[0-9]+\.[0-9]+.*?)/") { $version = $Matches["version"] } } $props = [ordered]@{ project = $p.project; ref = $r; refType = $r.type; version = $version; IsProjectValid = $true } $result += new-object -type pscustomobject -property $props } } else { $isvalid = $true if ($p.csproj -eq $null) { $isvalid = $false } $props = [ordered]@{ project = $p.project; ref = $null; refType = $null; version = $null; IsProjectValid = $isvalid } $result += new-object -type pscustomobject -property $props } } return $result } function test-sln { [CmdletBinding(DefaultParameterSetName = "sln")] param( [Parameter(Mandatory=$false, ParameterSetName="sln",Position=0)][Sln]$sln, [Parameter(Mandatory=$false, ParameterSetName="slnfile",Position=0)][string]$slnfile, [switch][bool] $missing, [switch][bool] $validate, $filter = $null ) if ($sln -eq $null) { if ([string]::IsNullOrEmpty($slnfile)) { $slns = @(get-childitem "." -Filter "*.sln") if ($slns.Length -eq 1) { $slnfile = $slns[0].fullname } else { if ($slns.Length -eq 0) { throw "no sln file given and no *.sln found in current directory" } else { throw "no sln file given and more than one *.sln file found in current directory" } } } if ($slnfile -eq $null) { throw "no sln file given and no *.sln found in current directory" } $sln = import-sln $slnfile } $deps = get-slndependencies $sln if ($filter -ne $null) { $deps = $deps | ? { if (!($_.ref.ShortName -match $filter)) { write-verbose "$($_.ref.ShortName) does not match filter:$filter" } return $_.ref.ShortName -match $filter } } $missingdeps = @($deps | ? { $_.IsProjectValid -eq $false -or ($_.ref -ne $null -and $_.ref.IsValid -eq $false) }) if ($missing) { return $missingdeps } if ($validate) { return $missingdeps.length -eq 0 } return $deps } function test-slndependencies { [CmdletBinding(DefaultParameterSetName = "sln")] param( [Parameter(Mandatory=$true, ParameterSetName="sln",Position=0)][Sln]$sln, [Parameter(Mandatory=$true, ParameterSetName="slnfile",Position=0)][string]$slnfile ) if ($sln -eq $null) { $sln = import-sln $slnfile } $deps = get-slndependencies $sln $valid = $true $missing = @() foreach($d in $deps) { if ($d.ref -ne $null -and $d.ref.IsValid -eq $false) { $valid = $false $missing += new-object -type pscustomobject -property @{ Ref = $d.ref; In = $d.project.fullname } } if ($d.isprojectvalid -eq $false) { $valid = $false $missing += new-object -type pscustomobject -property @{ Ref = $d.project; In = $sln.fullname } } } return $valid,$missing } function find-reporoot($path = ".") { $found = find-upwards ".git",".hg" -path $path if ($found -ne $null) { return split-path -Parent $found } else { return $null } } function find-globaljson($path = ".") { return find-upwards "global.json" -path $path } function find-matchingprojects { [CmdletBinding()] param ( [Parameter(Mandatory=$true)]$missing, [Parameter(Mandatory=$true)]$reporoot ) if (test-path (join-path $reporoot ".projects.json")) { $script:projects = get-content (join-path $reporoot ".projects.json") | out-string | convertfrom-jsonnewtonsoft write-verbose "getting csproj from cached file .projects.json" $csprojs = $script:projects.GetEnumerator() | ? { #ignore non-existing projects from ".projects.json" test-path (join-path $reporoot $_.value.path) } | % { get-item (join-path $reporoot $_.value.path) } } else { $csprojs = get-childitem "$reporoot" -Filter "*.csproj" -Recurse } #$csprojs | select -expandproperty name | format-table | out-string | write-verbose $packagesdir = find-packagesdir $reporoot write-verbose "found $($csprojs.length) csproj files in repo root '$reporoot' and subdirs. pwd='$(pwd)'. Packagesdir = '$packagesdir'" $missing = $missing | % { $m = $_ $matching = $null if ($m.ref.type -eq "project" -or $m.ref.type -eq "csproj") { $matching = @($csprojs | ? { [System.io.path]::GetFilenameWithoutExtension($_.Name) -eq $m.ref.Name }) $null = $m | add-property -name "matching" -value $matching #write-verbose "missing: $_.Name matching: $matching" } if ($m.ref.type -eq "nuget") { if ($m.ref.path -match "^(?<packages>.*packages[/\\])(?<pkg>.*)") { $matchingpath = join-path $packagesdir $matches["pkg"] if (test-path $matchingpath) { $matching = get-item $matchingpath } else { $matching = new-object -type pscustomobject -property @{ fullname = $matchingpath } } $null = $m | add-property -name "matching" -value $matching #write-verbose "missing: $_.Name matching: $matching" } } if ($matching -eq $null) { write-verbose "no project did match '$($m.ref.Name)' reference of type $($m.ref.type)" } else { write-verbose "found $(@($matching).Length) matching projects for '$($m.ref.Name)' reference of type $($m.ref.type):" $matching | % { write-verbose " $($_.fullname)" } } return $m } return $missing } function repair-slnpaths { [CmdletBinding(DefaultParameterSetName = "sln")] param( [Parameter(Mandatory=$false, ParameterSetName="sln",Position=0)][Sln]$sln, [Parameter(Mandatory=$false, ParameterSetName="slnfile",Position=0)][string]$slnfile, [Parameter(Position=1)] $reporoot, [Parameter(Position=2)] $filter = $null, [switch][bool] $tonuget, [switch][bool] $insln, [switch][bool] $incsproj, [switch][bool] $removemissing, [switch][bool] $prerelease ) if ($sln -eq $null) { if ([string]::IsNullOrEmpty($slnfile)) { $slns = @(get-childitem "." -Filter "*.sln") if ($slns.Length -eq 1) { $slnfile = $slns[0].fullname } else { if ($slns.Length -eq 0) { throw "no sln file given and no *.sln found in current directory" } else { throw "no sln file given and more than one *.sln file found in current directory" } } } if ($slnfile -eq $null) { throw "no sln file given and no *.sln found in current directory" } $sln = import-sln $slnfile } if (!$incsproj.IsPresent -and !$insln.ispresent) { $incsproj = $true $insln = $true } if ($insln) { $valid,$missing = test-slndependencies $sln write-verbose "SLN: found $($missing.length) missing projects" if ($reporoot -eq $null) { $reporoot = find-reporoot $sln.fullname if ($reporoot -ne $null) { write-host "auto-detected repo root at $reporoot" } } if ($reporoot -eq $null) { throw "No repository root given and none could be detected" } write-host "Fixing SLN..." write-verbose "looking for csprojs in reporoot..." $missing = find-matchingprojects $missing $reporoot if ($filter -ne $null) { $missing = $missing | ? { #if (!($_.ref.ShortName -match $filter)) { write-verbose "$($_.ref.ShortName) does not match filter:$filter" } return $_.ref.ShortName -match $filter } } $fixed = @{} foreach($_ in $missing) { if ($fixed.ContainsKey($_.ref.name)) { write-verbose "skipping fixed reference $($_.ref.name)" continue } if ($_.ref.Type -ne "project" -and $_.ref.Type -ne "csproj") { write-verbose "skipping non-project reference '$($_.ref.name)' of type '$($_.ref.type)'" continue } write-verbose "trying to fix missing SLN reference '$($_.ref.name)'" if ($_.matching -eq $null -or $_.matching.length -eq 0) { write-warning "no matching project found for SLN item $($_.ref.name)" if ($removemissing) { write-warning "removing $($_.ref.Path)" remove-slnproject $sln $($_.ref.Name) -ifexists $sln.Save() } } else { $matching = $_.matching if (@($matching).length -gt 1) { write-host "found $($matching.length) matching projects for $($_.ref.name). Choose one:" $i = 1 $matching = $matching | sort FullName $matching | % { write-host " $i. $($_.fullname)" $i++ } $c = read-host $matching = $matching[[int]$c-1] } if ($_.ref -is [slnproject]) { $relpath = get-relativepath $sln.fullname $matching.fullname write-host "Fixing bad SLN reference: $($_.ref.Path) => $relpath" $_.ref.Path = $relpath update-slnproject $sln $_.ref $fixed[$_.ref.name] = $true } elseif ($_.ref -isnot [referencemeta]) { $relpath = get-relativepath $sln.fullname $matching.fullname write-host "Adding missing SLN reference: $($_.ref.Path) => $relpath" $csp = import-csproj $matching.fullname add-slnproject $sln -name $csp.Name -path $relpath -projectguid $csp.guid $fixed[$_.ref.name] = $true } else { $relpath = get-relativepath $sln.fullname $matching.fullname write-host "Adding missing SLN reference: $($_.ref.Path) => $relpath" $csp = import-csproj $matching.fullname add-slnproject $sln -name $csp.Name -path $relpath -projectguid $csp.guid $fixed[$_.ref.name] = $true #write-warning "Don't know what to do with $($_.ref) of type $($_.ref.GetType())" } } } write-host "saving sln" $sln.Save() } if ($incsproj) { write-host "Fixing CSPROJs..." $projects = get-slnprojects $sln | ? { $_.type -eq "csproj" } if ($tonuget) { $pkgdir =(find-packagesdir $reporoot) if (!(test-path $pkgdir)) { $null = new-item -type Directory $pkgdir } $missing = test-sln $sln -missing $missing = @($missing | ? { $_.ref.type -eq "project"}) $missing = $missing | % { $_.ref.name } | sort -Unique $missing | % { try { write-host "replacing $_ with nuget" $found = find-nugetPath $_ $pkgdir if ($found -eq $null) { write-host "installing package $_" nuget install $_ -out $pkgdir -pre } convert-referencestonuget $sln -projectName $_ -packagesDir $pkgdir -filter $filer } catch { write-error $_ } } } foreach($_ in $projects) { if (test-path $_.fullname) { $csproj = import-csproj $_.fullname if (!$tonuget) { $null = repair-csprojpaths $csproj -reporoot $reporoot -prerelease:$prerelease } } } } # $valid,$missing = test-slndependencies $sln # $valid | Should Be $true } function get-csprojdependencies { [CmdletBinding(DefaultParameterSetName = "csproj")] param( [Parameter(Mandatory=$true, ParameterSetName="csproj",Position=0)][Csproj]$csproj, [Parameter(Mandatory=$true, ParameterSetName="csprojfile",Position=0)][string]$csprojfile ) if ($csproj -eq $null) { $csproj = import-csproj $csprojfile } $refs = @() $refs += get-projectreferences $csproj $refs += get-nugetreferences $csproj $refs = $refs | % { $r = $_ $props = [ordered]@{ ref = $r; refType = $r.type; path = $r.path } return new-object -type pscustomobject -property $props } return $refs } function repair-csprojpaths { [CmdletBinding(DefaultParameterSetName = "csproj")] param( [Parameter(Mandatory=$true, ParameterSetName="csproj",Position=0)][Csproj]$csproj, [Parameter(Mandatory=$true, ParameterSetName="csprojfile",Position=0)][string]$csprojfile, $reporoot = $null, [switch][bool] $prerelease ) if ($csproj -eq $null) { $csproj = import-csproj $csprojfile } $deps = get-csprojdependencies $csproj $missing = @($deps | ? { $_.ref.IsValid -eq $false }) write-verbose "($($csproj.name)): CSPROJ found $($missing.length) missing projects" if ($reporoot -eq $null) { $reporoot = find-reporoot $csproj.fullname if ($reporoot -ne $null) { write-verbose "($($csproj.name)): auto-detected repo root at $reporoot" } } if ($reporoot -eq $null) { throw "($($csproj.name)): No repository root given and none could be detected" } if ($missing.length -gt 0) { write-verbose "($($csproj.name)): looking for projects matching missing references" $missing = find-matchingprojects $missing $reporoot $missing | % { if ($_.matching -eq $null -or $_.matching.length -eq 0) { write-warning "($($csproj.name)): no matching project found for CSPROJ reference $($_.ref.Path)" } else { $relpath = get-relativepath $csproj.fullname $_.matching.fullname $_.ref.Path = $relpath if ($_.ref.type -eq "project" -and $_.ref.Node.Include -ne $null) { write-verbose "($($csproj.name)): fixing CSPROJ reference in $($csproj.name): $($_.ref.Path) => $relpath" $_.ref.Node.Include = $relpath } if ($_.ref.type -eq "nuget" -and $_.ref.Node.HintPath -ne $null) { write-verbose "($($csproj.name)): fixing NUGET reference in $($csproj.name): $($_.ref.Path) => $relpath" $_.ref.Node.HintPath = $relpath } } } $csproj.Save() write-verbose "($($csproj.name)): missing references done" } $pkgdir = find-packagesdir $reporoot $dir = split-path -parent $csproj.FullName if (test-path (Join-Path $dir "packages.config")) { write-verbose "($($csproj.name)): checking packages.config" $pkgs = get-packagesconfig (Join-Path $dir "packages.config") $pkgs = $pkgs.packages $isInsideVS = (get-command "install-package" -Module nuget -errorAction Ignore) -ne $null if ($isInsideVS) { write-verbose "($($csproj.name)): detected Nuget module. using Nuget/install-package" foreach($dep in $pkgs) { nuget\install-package -ProjectName $csproj.name -id $dep.id -version $dep.version -prerelease:$prerelease } } else { $refs = get-nugetreferences $csproj foreach($pkgref in $pkgs) { $ref = $refs | ? { $_.ShortName -eq $pkgref.id } if ($ref -eq $null) { $nugetpath = find-nugetpath $pkgref.id $pkgdir -versionhint $pkgref.version if ($nugetpath -ne $null) { $dllname = [System.IO.Path]::GetFileNameWithoutExtension($nugetpath.PAth) $ref = $refs | ? { $_.ShortName -eq $dllname } if ($ref -ne $null) { #write-verbose "$($pkgref.id) maps to $($nugetpath.PAth)" } } if ($ref -eq $null) { write-warning "($($csproj.name)): missing csproj reference for package $($pkgref.id)" } } if ($ref.path -notmatch "$($pkgref.version)\\") { # bad reference in csproj? try to detect current version if ($ref.path -match "$($pkgref.id).(?<version>.*?)\\") { Write-Warning "($($csproj.name)): version of package '$($pkgref.id)' in csproj: '$($matches["version"])' doesn't match packages.config version: '$($pkgref.version)'. Fixing" # fix it $ref.path = $ref.path -replace "$($pkgref.id).(?<version>.*?)\\","$($pkgref.id).$($pkgref.version)\" write-verbose "corrected path: $($ref.path)" $ref.Node.HintPath = $ref.path $inc = $ref.Node.Include if ($inc -ne $null -and $inc -match "$($pkgref.id),\s*Version=(.*?),") { write-verbose "($($csproj.name)): fixing include tag" $inc = $inc -replace "($($pkgref.id)),\s*Version=(.*?),",'$1,' write-verbose "corrected include: $($ref.path)" $ref.Node.Include = $inc } $csproj.save() } } } } } # $valid,$missing = test-slndependencies $sln # $valid | Should Be $true } function update-nuget { param([Parameter(mandatory=$true,ValueFromPipeline=$true)]$id, $version) process { write-verbose "checking packages.config" $pkgconfig = get-packagesconfig ("packages.config") $pkgs = $pkgconfig.packages $changed = $false foreach($pkgid in @($id)) { $ver = $version if ($pkgid -match "(?<id>.*)\.(?<version>[0-9]+\.[0-9]+\.[0-9]+.*)") { $ver = $matches["version"] $pkgid = $matches["id"] } else { write-warning "please specify package version with -version or in package id (like '$pkgid.1.0.0')" continue } if ($ver -eq $null) { } $ref = $pkgs | ? { $_.id -eq $pkgid } if ($ref -ne $null) { $changed = $true $ref.version = $ver } else { write-warning "package $pkgid not found in packages.config" continue } } if ($changed) { $pkgconfig | set-packagesconfig -outfile "packages.config" get-childitem . -filter "*.csproj" | %{ fix-csproj $_.FullName } } } } new-alias fix-sln repair-slnpaths new-alias fixsln fix-sln new-alias fix-csproj repair-csprojpaths new-alias fixcsproj fix-csproj |