functions/csproj-utils.ps1
$script:ns = 'http://schemas.microsoft.com/developer/msbuild/2003' $script:types = @" using System.Xml; public class Csproj { public System.Xml.XmlDocument Xml {get;set;} public string Path {get;set;} public string FullName { get { return Path; } } public string Name {get;set;} public string Guid {get;set;} public override string ToString() { return Name; } public void Save() { this.Xml.Save(this.Path); } public void Save(string path) { this.Xml.Save(path); } public void Save(System.IO.TextWriter w) { this.Xml.Save(w); } } public class ReferenceMeta { public System.Xml.XmlElement Node {get;set;} public string Name {get;set;} public string Version {get;set;} public string ShortName {get;set;} public string Type {get;set;} public string Path {get;set;} public bool? IsValid {get;set;} public override string ToString() { return string.Format("-> {1}{0}", ShortName, IsValid != null ? ((IsValid.Value ? "[+]" : "[-]") + " ") : ""); } } "@ add-type -TypeDefinition $types -ReferencedAssemblies "System.Xml" function import-csproj { [OutputType([Csproj])] param([Parameter(ValueFromPipeline=$true)]$file) $path = $null if (test-path $file) { $content = get-content $file $path = (get-item $file).FullName $name = [System.IO.Path]::GetFilenameWithoutExtension($file) } elseif ($file.Contains("<?xml") -or $file.Contains("<Project")) { $content = $file } else { throw "csproj file not found: '$file'" } try { $xml =[xml]$content } catch { throw "failed to parse project '$file': $_" } try { $guidNode = $xml | get-nodes -nodeName "ProjectGuid" $guid = $guidnode.Node.InnerText } catch { Write-Warning $_ throw "failed to find ProjectGuid: $($_.Exception.Message)" } $csproj = new-object -type csproj -Property @{ xml = $xml path = $path name = $name guid = $guid } return $csproj } function get-referenceName($node) { if ($node.HasAttribute("Name")) { return $node.GetAttribute("Name") } if ($node.GetElementsByTagName("Name") -ne $null) { return $node.Name } if ($node.Include -ne $null) { return $node.Include } } function add-metadata { param( [parameter(ValueFromPipeline=$true)]$nodes, [Csproj]$csproj ) process { $n = $nodes if ($nodes.Node) { $n = $nodes.Node } $name = get-referenceName $n # $n.Name can be hidden by dynamic property from Name attribute/child $type = switch($n.get_Name()) { "ProjectReference" { "project" } "Reference" { "dll" } default { "?" } } $path = $null #TODO: Reference node may have more than one HintPath! if ($n.HintPath) { $path = $n.HintPath } elseif ($n.Include) { $path = $n.Include } if ($path -is [System.Array]) { #hopefully, the last path will be a nuget package $path = $path[$path.length -1] } $isvalid = $null $abspath = $path if ($abspath -ne $null ` -and (test-ispathrelative $abspath) ` -and $csproj -ne $null ` -and ![string]::IsNullOrEmpty($csproj.fullname)) { try { $abspath = join-path (split-path -parent $csproj.fullname) $abspath } catch { write-error $_.ScriptStackTrace throw "failed to find abs path of $csproj ($($csproj.length)): $_" } } if (!(test-ispathrelative $abspath)) { $isvalid = test-path $abspath } return new-object -TypeName ReferenceMeta -Property @{ Node = $n Name = $name ShortName = get-shortname $name Type = $type Path = $path IsValid = $isvalid } } } function get-nodes([Parameter(ValueFromPipeline=$true)][xml] $xml, $nodeName) { $r = Select-Xml -Xml $xml.Project -Namespace @{ d = $ns } -XPath "//d:$nodeName" return $r } function get-referencenodes([Parameter(ValueFromPipeline=$true)][xml] $xml, $nodeName, [switch][bool]$noMeta, [Csproj] $csproj) { $r = get-nodes $xml $nodename if (!$nometa) { $meta = $r | add-metadata -csproj $csproj } return $meta } function get-projectreferences([Parameter(ValueFromPipeline=$true, Mandatory=$true)][csproj] $csproj) { return get-referencenodes $csproj.xml "ProjectReference" -csproj $csproj } function get-allexternalreferences([Parameter(ValueFromPipeline=$true, Mandatory=$true)][csproj] $csproj) { get-referencenodes $csproj.xml "Reference[d:HintPath]" -csproj $csproj } function get-externalreferences([Parameter(ValueFromPipeline=$true, Mandatory=$true)][Csproj] $csproj) { $refs = get-allexternalreferences $csproj $refs = $refs | ? { $_.Node.HintPath -notmatch "[""\\/]packages[/\\]" } return $refs } function get-nugetreferences([Parameter(ValueFromPipeline=$true, Mandatory=$true)][csproj] $csproj) { $refs = get-allexternalreferences $csproj $refs = $refs | ? { $_.Node.HintPath -match "[""\\/]packages[/\\]" } $refs = $refs | % { $_.type = "nuget" $_ } return $refs } function get-systemreferences([Parameter(ValueFromPipeline=$true, Mandatory=$true)][csproj] $csproj) { get-referencenodes $csproj.xml "Reference[not(d:HintPath)]" -csproj $csproj } function get-allreferences([Parameter(ValueFromPipeline=$true, Mandatory=$true)][csproj] $csproj) { $refs = @() $refs += get-systemreferences $csproj $refs += get-nugetreferences $csproj $refs += get-externalreferences $csproj return $refs } function remove-node([Parameter(ValueFromPipeline=$true)]$node) { $node.ParentNode.RemoveChild($node) } function new-projectReferenceNode([System.Xml.xmldocument]$document) { <# <ProjectReference Include="..\xxx\xxx.csproj"> <Project>{89c414d8-0258-4a94-8e45-88b338c15e7a}</Project> <Name>xxx</Name> </ProjectReference> #> $projectRef = [System.Xml.XmlElement]$document.CreateNode([System.Xml.XmlNodeType]::Element, "", "ProjectReference", $ns) $includeAttr = [System.Xml.XmlAttribute]$document.CreateAttribute("Include") #$nugetref = [System.Xml.XmlElement]$document.CreateElement("Reference"); $projectGuid = [System.Xml.XmlElement]$document.CreateNode([System.Xml.XmlNodeType]::Element, "", "Project", $ns) $projectName = [System.Xml.XmlElement]$document.CreateNode([System.Xml.XmlNodeType]::Element, "", "Name", $ns) $null = $projectRef.Attributes.Append($includeAttr) $null = $projectRef.AppendChild($projectName) $null = $projectRef.AppendChild($projectGuid) return $projectRef } function new-referenceNode([System.Xml.xmldocument]$document) { $nugetref = [System.Xml.XmlElement]$document.CreateNode([System.Xml.XmlNodeType]::Element, "", "Reference", $ns) #$nugetref = [System.Xml.XmlElement]$document.CreateElement("Reference"); $includeAttr = [System.Xml.XmlAttribute]$document.CreateAttribute("Include") $hint = [System.Xml.XmlElement]$document.CreateNode([System.Xml.XmlNodeType]::Element, "", "HintPath", $ns) $hint.InnerText = "hint" $null = $nugetref.Attributes.Append($includeAttr) $null = $nugetref.AppendChild($hint) return $nugetref } function add-projectItem { [CmdletBinding()] param ([Parameter(Mandatory=$true)] $csproj, [Parameter(Mandatory=$true)] $file) ipmo pathutils if ($csproj -is [string]) { $csprojPath = $csproj $document = (import-csproj $csprojPath).xml } else { $document = $csproj.xml } if ($csprojPath -ne $null) { $file = Get-RelativePath -Dir (split-path -Parent $csprojPath) $file } <# <ItemGroup> <None Include="NowaEra.XPlatform.Sync.Client.nuspec"> <SubType>Designer</SubType> </None> <None Include="packages.config"> <SubType>Designer</SubType> </None> </ItemGroup> #> $itemgroup = [System.Xml.XmlElement]$document.CreateNode([System.Xml.XmlNodeType]::Element, "", "ItemGroup", $ns); $none = [System.Xml.XmlElement]$document.CreateNode([System.Xml.XmlNodeType]::Element, "", "None", $ns); $null = $itemgroup.AppendChild($none) $includeAttr = [System.Xml.XmlAttribute]$document.CreateAttribute("Include"); $null = $none.Attributes.Append($includeAttr); $none.Include = "$file" write-verbose "adding item '$file': $($itemgroup.OuterXml)" $other = get-nodes $document -nodeName "ItemGroup" if ($other.Count -gt 0) { $last = ([System.Xml.XmlNode]$other[$other.Count - 1].Node) $null = $last.ParentNode.InsertAfter($itemgroup, $last) } else { $null = $document.AppendChild($itemgroup) } if ($csprojPath -ne $null) { write-verbose "saving project '$csprojPath'" -verbref $VerbosePreference $document.Save($csprojPath) } } function convertto-nugetreference { [OutputType([ReferenceMeta])] param( [Parameter(Mandatory=$true)] [ReferenceMeta] $ref, [Parameter(Mandatory=$true)] [string ]$packagesRelPath ) ipmo nupkg $node = get-asnode $ref $projectPath = $node.Include $projectId = $node.Project $projectName = $node.Name $nuget = find-nugetPath $projectName $packagesRelPath $path = $nuget.Path $version = $nuget.LatestVersion $framework = $nuget.Framework if ($path -eq $null) { throw "package '$projectName' not found in packages dir '$packagesRelPath'" } $nugetref = new-referenceNode $node.OwnerDocument $nugetref.Include = $projectName $hintNode = $nugetref.ChildNodes | ? { $_.Name -eq "HintPath" } $hintNode.InnerText = $path #$nugetref.hintpath = $path $meta = $nugetref | add-metadata $meta.Version = $version return $meta } function convertto-projectreference { [OutputType([ReferenceMeta])] param( [Parameter(Mandatory=$true)] [ReferenceMeta] $ref, [Parameter(Mandatory=$true)] [string ]$targetProject ) $node = get-asnode $ref <# <ProjectReference Include="..\xxx\xxx.csproj"> <Project>{89c414d8-0258-4a94-8e45-88b338c15e7a}</Project> <Name>xxx</Name> </ProjectReference> #> # TODO: handle relative path $targetcsproj = import-csproj $targetProject $guidNode = $targetcsproj.xml | get-nodes -nodeName "ProjectGuid" $guid = $guidnode.Node.InnerText $projectRef = new-projectReferenceNode $node.OwnerDocument $projectRef.Include = $targetProject $projectRef.Name = [System.IO.Path]::GetFilenameWithoutExtension($targetProject) $projectRef.Project = $guid $meta = $projectRef | add-metadata return $meta } function convert-reference { [CmdletBinding()] param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)][csproj] $csproj, [Parameter(Mandatory=$true)][referenceMeta] $originalref, [Parameter(Mandatory=$true)][referenceMeta] $newref ) #$originalref = $originalref | get-asnode #$newref = $newref | get-asnode $null = $originalref.Node.parentNode.AppendChild($newref.Node) $null = $originalref.Node.parentNode.RemoveChild($originalref.Node) write-verbose "replacing:" write-verbose "`r`n$($originalref.Node.OuterXml)" write-verbose "with:" write-verbose "`r`n$($newref.Node.OuterXml)" if ($csproj.path -ne $null -and $newref.Type -ne "project") { $dir = split-path -Parent $csproj.path $pkgs = get-packagesconfig (Join-Path $dir "packages.config") -createifnotexists add-packagetoconfig -packagesconfig $pkgs -package $newref.Name -version $newref.Version -ifnotexists #make sure paths are relative $newref.Node.HintPath = (Get-RelativePath $dir $newref.Node.HintPath) $pkgs.xml.Save( (Join-Path $dir "packages.config") ) } else { write-warning "passed csproj path==null. Cannot edit packages.config" } } function get-asnode { param([Parameter(Mandatory=$true, ValueFromPipeline=$true)]$ref) if ($ref -is [System.Xml.XmlNode]) {return $ref } elseif ($ref.Node -ne $null) { return $ref.Node } else { throw "$ref is not a node and has no 'Node' property"} } function get-project($name, [switch][bool]$all) { $projs = gci -Filter "*.csproj" if ($name -ne $null) { $projs = $projs | ? { $_.Name -eq "$name.csproj" } } $projs = $projs | % { new-object -type pscustomobject -Property @{ FullName = $_.FullName File = $_ Name = $_.Name } } return $projs } new-alias replace-reference convert-reference new-alias convertto-nuget convertto-nugetreference |