diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj
index d27a5fb43d7..5ff7b83c675 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj
+++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj
@@ -67,7 +67,7 @@
-
+
diff --git a/test/common/package/package.tests.ps1 b/test/common/package/package.tests.ps1
new file mode 100644
index 00000000000..c7663ec1fe2
--- /dev/null
+++ b/test/common/package/package.tests.ps1
@@ -0,0 +1,68 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+$moduleRootFilePath = Split-Path -Path $PSScriptRoot -Parent
+
+# Identify the repository root path of the resource module
+$repoRootPath = (Resolve-Path -LiteralPath (Join-path $moduleRootFilePath "../..")).ProviderPath
+
+Import-Module "$repoRootPath/tools/releaseTools.psm1"
+
+Describe 'Common Tests - Package Reference' -Tag 'CI' {
+ BeforeAll {
+ $testCases = @()
+ Get-NewOfficalPackage -IncludeAll | ForEach-Object {
+ $testCases += @{
+ CsProj = $_.CsProj
+ PackageName = $_.PackageName
+ CsProjVersion = $_.CsProjVersion
+ NuGetRevision = $_.NuGetRevision
+ NuGetVersion = $_.NuGetVersion
+ }
+ }
+ }
+
+ # This test should always be enabled
+ It " reference to should not need to be updated by a revision" -TestCases $testCases {
+ param(
+ [string]
+ $CsProj,
+
+ [string]
+ $PackageName,
+
+ [string]
+ $CsProjVersion,
+
+ [string]
+ $NuGetRevision,
+
+ [string]
+ $NuGetVersion
+ )
+
+ $NuGetRevision | Should -BeExactly $CsProjVersion
+ }
+
+ # This test should be enabled when we are developing
+ It " reference to should not need to be updated by a new version" -TestCases $testCases {
+ param(
+ [string]
+ $CsProj,
+
+ [string]
+ $PackageName,
+
+ [string]
+ $CsProjVersion,
+
+ [string]
+ $NuGetRevision,
+
+ [string]
+ $NuGetVersion
+ )
+
+ $NuGetVersion | Should -BeExactly $CsProjVersion
+ }
+}
diff --git a/tools/Xml/Xml.psm1 b/tools/Xml/Xml.psm1
new file mode 100644
index 00000000000..1184dfc7a6a
--- /dev/null
+++ b/tools/Xml/Xml.psm1
@@ -0,0 +1,101 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+# Adds an attribute to a XmlElement
+function New-XmlAttribute
+{
+ param(
+ [Parameter(Mandatory)]
+ [string]$Name,
+ [Parameter(Mandatory)]
+ [object]$Value,
+ [Parameter(Mandatory)]
+ [System.Xml.XmlElement]$Element,
+ [Parameter(Mandatory)]
+ [System.Xml.XmlDocument]$XmlDoc
+ )
+
+ $attribute = $XmlDoc.CreateAttribute($Name)
+ $attribute.Value = $value
+ $null = $Element.Attributes.Append($attribute)
+}
+
+# Adds an XmlElement to an XmlNode
+# Returns the new Element
+function New-XmlElement
+{
+ param(
+ [Parameter(Mandatory)]
+ [string]$LocalName,
+ [Parameter(Mandatory)]
+ [System.Xml.XmlDocument]$XmlDoc,
+ [Parameter(Mandatory)]
+ [System.Xml.XmlNode]$Node,
+ [Switch]$PassThru,
+ [string]$NamespaceUri
+ )
+
+ if($NamespaceUri)
+ {
+ $newElement = $XmlDoc.CreateElement($LocalName, $NamespaceUri)
+ }
+ else
+ {
+ $newElement = $XmlDoc.CreateElement($LocalName)
+ }
+
+ $null = $Node.AppendChild($newElement)
+ if($PassThru.IsPresent)
+ {
+ return $newElement
+ }
+}
+
+# Removes an XmlElement and it's parent if it is empty
+function Remove-XmlElement
+{
+ param(
+ [Parameter(Mandatory)]
+ [System.Xml.XmlElement]$Element,
+ [Switch]$RemoveEmptyParents
+ )
+
+ $parentNode = $Element.ParentNode
+ $null = $parentNode.RemoveChild($Element)
+ if(!$parentNode.HasChildNodes -and $RemoveEmptyParent.IsPresent)
+ {
+ Remove-XmlElement -Element $parentNode -RemoveEmptyParents
+ }
+}
+
+# Get a node by XPath
+# Returns null if the node is not found
+function Get-XmlNodeByXPath
+{
+ param(
+ [Parameter(Mandatory)]
+ [System.Xml.XmlDocument]
+ $XmlDoc,
+ [System.Xml.XmlNamespaceManager]
+ $XmlNsManager,
+ [Parameter(Mandatory)]
+ [string]
+ $XPath
+ )
+
+ if($XmlNsManager)
+ {
+ return $XmlDoc.SelectSingleNode($XPath,$XmlNsManager)
+ }
+ else
+ {
+ return $XmlDoc.SelectSingleNode($XPath)
+ }
+}
+
+Export-ModuleMember -Function @(
+ 'Get-XmlNodeByXPath'
+ 'Remove-XmlElement'
+ 'New-XmlElement'
+ 'New-XmlAttribute'
+)
diff --git a/tools/appveyor.psm1 b/tools/appveyor.psm1
index 6ae6ef4b040..5a6f4649c83 100644
--- a/tools/appveyor.psm1
+++ b/tools/appveyor.psm1
@@ -626,6 +626,7 @@ function Invoke-AppveyorFinish
}
catch {
Write-Host -Foreground Red $_
+ Write-Host -Foreground Red $_.ScriptStackTrace
throw $_
}
}
diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1
index 0f1adc723a3..63fd068aec1 100644
--- a/tools/packaging/packaging.psm1
+++ b/tools/packaging/packaging.psm1
@@ -3,6 +3,7 @@
$Environment = Get-EnvironmentInformation
$packagingStrings = Import-PowerShellDataFile "$PSScriptRoot\packaging.strings.psd1"
+Import-Module "$PSScriptRoot\..\Xml" -ErrorAction Stop -Force
$DebianDistributions = @("ubuntu.14.04", "ubuntu.16.04", "ubuntu.17.10", "ubuntu.18.04", "debian.8", "debian.9")
function Start-PSPackage {
@@ -2554,15 +2555,19 @@ function Test-FileWxs
$filesAssetString = (Get-Content -Raw -Path $FilesWxsPath).Replace('$(var.FileArchitecture)',$env:FileArchitecture)
[xml] $filesAssetXml = $filesAssetString
+ [xml] $newFilesAssetXml = $filesAssetString
+ $xmlns=[System.Xml.XmlNamespaceManager]::new($newFilesAssetXml.NameTable)
+ $xmlns.AddNamespace('Wix','http://schemas.microsoft.com/wix/2006/wi')
+
[xml] $heatFilesXml = Get-Content -Raw -Path $HeatFilesWxsPath
$assetFiles = $filesAssetXml.GetElementsByTagName('File')
$heatFiles = $heatFilesXml.GetElementsByTagName('File')
- $indexedHeatFiles = @()
+ $heatNodesByFile = @{}
# Index the list of files generated by heat
foreach($file in $heatFiles)
{
- $indexedHeatFiles += $file.Source
+ $heatNodesByFile.Add($file.Source, $file)
}
# Index the files from the asset wxs
@@ -2572,42 +2577,218 @@ function Test-FileWxs
foreach($file in $assetFiles)
{
$name = $file.Source
- if($indexedHeatFiles -inotcontains $name)
+ if($heatNodesByFile.Keys -inotcontains $name)
{
$passed = $false
Write-Warning "{$name} is no longer in product and should be removed from {$FilesWxsPath}"
+ $componentId = $file.ParentNode.Id
+ $componentXPath = '//Wix:Component[@Id="{0}"]' -f $componentId
+ $componentNode = Get-XmlNodeByXPath -XmlDoc $newFilesAssetXml -XmlNsManager $xmlns -XPath $componentXPath
+ if($componentNode)
+ {
+ # Remove the Component
+ Remove-XmlElement -Element $componentNode -RemoveEmptyParents
+ # Remove teh ComponentRef
+ Remove-ComponentRefNode -Id $componentId -XmlDoc $newFilesAssetXml -XmlNsManager $xmlns
+ }
+ else
+ {
+ Write-Warning "Could not remove this node!"
+ }
}
$indexedAssetFiles += $name
}
# verify that no files have been added.
- foreach($file in $indexedHeatFiles)
+ foreach($file in $heatNodesByFile.Keys)
{
if($indexedAssetFiles -inotcontains $file)
{
$passed = $false
- Write-Warning "new file {$file} need to be added to {$FilesWxsPath}"
+ $folder = Split-Path -Path $file
+ $name = Split-Path -Path $file -Leaf
+ $heatNode = $heatNodesByFile[$file]
+ $compGroupNode = Get-ComponentGroupNode -XmlDoc $newFilesAssetXml -XmlNsManager $xmlns
+ $filesNode = Get-DirectoryNode -Node $heatNode -XmlDoc $newFilesAssetXml -XmlNsManager $xmlns
+ # Create new Component
+ $newComponent = New-XmlElement -XmlDoc $newFilesAssetXml -LocalName 'Component' -Node $filesNode -PassThru -NamespaceUri 'http://schemas.microsoft.com/wix/2006/wi'
+ $componentId = New-WixId -Prefix 'cmp'
+ New-XmlAttribute -XmlDoc $newFilesAssetXml -Element $newComponent -Name 'Id' -Value $componentId
+ New-XmlAttribute -XmlDoc $newFilesAssetXml -Element $newComponent -Name 'Guid' -Value "{$(New-Guid)}"
+ # Crete new File in Component
+ $newFile = New-XmlElement -XmlDoc $newFilesAssetXml -LocalName 'File' -Node $newComponent -PassThru -NamespaceUri 'http://schemas.microsoft.com/wix/2006/wi'
+ New-XmlAttribute -XmlDoc $newFilesAssetXml -Element $newFile -Name 'Id' -Value (New-WixId -Prefix 'fil')
+ New-XmlAttribute -XmlDoc $newFilesAssetXml -Element $newFile -Name 'KeyPath' -Value "yes"
+ New-XmlAttribute -XmlDoc $newFilesAssetXml -Element $newFile -Name 'Source' -Value $file
+ # Create new ComponentRef
+ $newComponentRef = New-XmlElement -XmlDoc $newFilesAssetXml -LocalName 'ComponentRef' -Node $compGroupNode -PassThru -NamespaceUri 'http://schemas.microsoft.com/wix/2006/wi'
+ New-XmlAttribute -XmlDoc $newFilesAssetXml -Element $newComponentRef -Name 'Id' -Value $componentId
+
+ Write-Warning "new file in {$folder} with name {$name} in a {$($filesNode.LocalName)} need to be added to {$FilesWxsPath}"
}
}
if(!$passed)
{
+ $newXmlFileName = Join-Path -Path $env:TEMP -ChildPath ([System.io.path]::GetRandomFileName() + '.xml')
+ $newFilesAssetXml.Save($newXmlFileName)
+ $newXml = Get-Content -raw $newXmlFileName
+ $newXml = $newXml -replace 'amd64', '$(var.FileArchitecture)'
+ $newXml = $newXml -replace 'x86', '$(var.FileArchitecture)'
+ $newXml | Out-File -FilePath $newXmlFileName -Encoding ascii
+ Write-Log -message "Update xml saved to $newXmlFileName"
if($env:appveyor)
{
try
{
- Push-AppveyorArtifact $HeatFilesWxsPath
+ Push-AppveyorArtifact $newXmlFileName
}
catch
{
Write-Warning -Message "Pushing MSI File fragment failed."
}
}
+ elseif($env:TF_BUILD -and $env:BUILD_REASON -ne 'PullRequest')
+ {
+ Write-Host "##vso[artifact.upload containerfolder=wix;artifactname=wix]$newXmlFileName"
+ }
throw "Current files to not match {$FilesWxsPath}"
}
}
+# Removes a ComponentRef node in the files.wxs Xml Doc
+function Remove-ComponentRefNode
+{
+ param(
+ [Parameter(Mandatory)]
+ [System.Xml.XmlDocument]
+ $XmlDoc,
+ [Parameter(Mandatory)]
+ [System.Xml.XmlNamespaceManager]
+ $XmlNsManager,
+ [Parameter(Mandatory)]
+ [string]
+ $Id
+ )
+
+ $compRefXPath = '//Wix:ComponentRef[@Id="{0}"]' -f $Id
+ $node = Get-XmlNodeByXPath -XmlDoc $XmlDoc -XmlNsManager $XmlNsManager -XPath $compRefXPath
+ if($node)
+ {
+ Remove-XmlElement -element $node
+ }
+ else
+ {
+ Write-Warning "could not remove node"
+ }
+}
+
+# Get the ComponentGroup node in the files.wxs Xml Doc
+function Get-ComponentGroupNode
+{
+ param(
+ [Parameter(Mandatory)]
+ [System.Xml.XmlDocument]
+ $XmlDoc,
+ [Parameter(Mandatory)]
+ [System.Xml.XmlNamespaceManager]
+ $XmlNsManager
+ )
+
+ if(!$XmlNsManager.HasNamespace('Wix'))
+ {
+ throw 'Namespace manager must have "wix" defined.'
+ }
+
+ $compGroupXPath = '//Wix:ComponentGroup'
+ $node = Get-XmlNodeByXPath -XmlDoc $XmlDoc -XmlNsManager $XmlNsManager -XPath $compGroupXPath
+ return $node
+}
+
+# Gets the Directory Node the files.wxs Xml Doc
+# Creates it if it does not exist
+function Get-DirectoryNode
+{
+ param(
+ [Parameter(Mandatory)]
+ [System.Xml.XmlElement]
+ $Node,
+ [Parameter(Mandatory)]
+ [System.Xml.XmlDocument]
+ $XmlDoc,
+ [Parameter(Mandatory)]
+ [System.Xml.XmlNamespaceManager]
+ $XmlNsManager
+ )
+
+ if(!$XmlNsManager.HasNamespace('Wix'))
+ {
+ throw 'Namespace manager must have "wix" defined.'
+ }
+
+ $pathStack = [System.Collections.Stack]::new()
+
+ [System.Xml.XmlElement] $dirNode = $Node.ParentNode.ParentNode
+ $dirNodeType = $dirNode.LocalName
+ if($dirNodeType -eq 'DirectoryRef')
+ {
+ return Get-XmlNodeByXPath -XmlDoc $XmlDoc -XmlNsManager $XmlNsManager -XPath "//Wix:DirectoryRef"
+ }
+ if($dirNodeType -eq 'Directory')
+ {
+ while($dirNode.LocalName -eq 'Directory') {
+ $pathStack.Push($dirNode.Name)
+ $dirNode = $dirNode.ParentNode
+ }
+ $path = "//"
+ [System.Xml.XmlElement] $lastNode = $null
+ while($pathStack.Count -gt 0){
+ $dirName = $pathStack.Pop()
+ $path += 'Wix:Directory[@Name="{0}"]' -f $dirName
+ $node = Get-XmlNodeByXPath -XmlDoc $XmlDoc -XmlNsManager $XmlNsManager -XPath $path
+
+ if(!$node)
+ {
+ if(!$lastNode)
+ {
+ # Inserting at the root
+ $lastNode = Get-XmlNodeByXPath -XmlDoc $XmlDoc -XmlNsManager $XmlNsManager -XPath "//Wix:DirectoryRef"
+ }
+
+ $newDirectory = New-XmlElement -XmlDoc $XmlDoc -LocalName 'Directory' -Node $lastNode -PassThru -NamespaceUri 'http://schemas.microsoft.com/wix/2006/wi'
+ New-XmlAttribute -XmlDoc $XmlDoc -Element $newDirectory -Name 'Name' -Value $dirName
+ New-XmlAttribute -XmlDoc $XmlDoc -Element $newDirectory -Name 'Id' -Value (New-WixId -Prefix 'dir')
+ $lastNode = $newDirectory
+ }
+ else
+ {
+ $lastNode = $node
+ }
+ if ($pathStack.Count -gt 0)
+ {
+ $path += '/'
+ }
+ }
+ return $lastNode
+ }
+
+ throw "unknown element type: $dirNodeType"
+}
+
+# Creates a new Wix Id in the proper format
+function New-WixId
+{
+ param(
+ [Parameter(Mandatory)]
+ [string]
+ $Prefix
+ )
+
+ $guidPortion = (New-Guid).Guid.ToUpperInvariant() -replace '\-' ,''
+ "$Prefix$guidPortion"
+}
+
# Builds coming out of this project can have version number as 'a.b.c' OR 'a.b.c-d-f'
# This function converts the above version into major.minor[.build[.revision]] format
function Get-PackageVersionAsMajorMinorBuildRevision
diff --git a/tools/releaseTools.psm1 b/tools/releaseTools.psm1
index 95c74dcd606..30395ed6683 100644
--- a/tools/releaseTools.psm1
+++ b/tools/releaseTools.psm1
@@ -223,6 +223,9 @@ function Get-ChangeLog
#.PARAMETER Path
#The path to check for csproj files with packagse
#
+#.PARAMETER IncludeAll
+#Include packages that don't need to be updated
+#
#.OUTPUTS
#Objects which represet the csproj package ref, with the current and new version
##############################
@@ -230,11 +233,13 @@ function Get-NewOfficalPackage
{
param(
[String]
- $Path = (Join-path -Path $PSScriptRoot -ChildPath '..')
+ $Path = (Join-path -Path $PSScriptRoot -ChildPath '..\src'),
+ [Switch]
+ $IncludeAll
)
# Calculate the filter to find the CSProj files
$filter = Join-Path -Path $Path -ChildPath '*.csproj'
- $csproj = Get-ChildItem $filter -Recurse
+ $csproj = Get-ChildItem $filter -Recurse -Exclude 'PSGalleryModules.csproj'
$csproj | ForEach-Object{
$file = $_
@@ -246,25 +251,58 @@ function Get-NewOfficalPackage
$packages=$csprojXml.Project.ItemGroup.PackageReference
# check to see if there is a newer package for each refernce
- foreach($package in $packages)
+ foreach ($package in $packages)
{
# Get the name of the package
$name = $package.Include
- # don't pull 'Microsoft.Management.Infrastructure' from nuget
- if($name -and $name -ne 'Microsoft.Management.Infrastructure')
+ if ($name)
{
# Get the current package from nuget
- $newPackage = find-package -Name $name -Source https://nuget.org/api/v2/ -ErrorAction SilentlyContinue
+ $versions = find-package -Name $name -Source https://nuget.org/api/v2/ -ErrorAction SilentlyContinue -AllVersions |
+ Add-Member -Type ScriptProperty -Name Published -Value { $this.Metadata['published']} -PassThru |
+ Where-Object { Test-IncludePackageVersion -NewVersion $_.Version -Version $package.version}
+
+ $revsionRegEx = Get-MatchingMajorMinorRegEx -Version $package.version
+ $newPackage = $versions |
+ Sort-Object -Descending |
+ Select-Object -First 1
+
+ # Get the newest matching revision
+ $newRevision = $versions |
+ Where-Object {$_.Version -match $revsionRegEx } |
+ Sort-Object -Descending |
+ Select-Object -First 1
# If the current package has a different version from the version in the csproj, print the details
- if($newPackage -and $newPackage.Version.ToString() -ne $package.version)
+ if ($newRevision -and $newRevision.Version.ToString() -ne $package.version -or $newPackage -and $newPackage.Version.ToString() -ne $package.version -or $IncludeAll.IsPresent)
{
+ if ($newRevision)
+ {
+ $newRevisionString = $newRevision.Version
+ }
+ else
+ {
+ # We don't have a new Revision, report the current version
+ $newRevisionString = $package.Version
+ }
+
+ if ($newPackage)
+ {
+ $newVersionString = $newPackage.Version
+ }
+ else
+ {
+ # We don't have a new Version, report the current version
+ $newVersionString = $package.Version
+ }
+
[pscustomobject]@{
- Csproj = $file
+ Csproj = (Split-Path -Path $file -Leaf)
PackageName = $name
CsProjVersion = $Package.Version
- NuGetVersion = $newPackage.Version
+ NuGetRevision = $newRevisionString
+ NuGetVersion = $newVersionString
}
}
}
@@ -272,6 +310,91 @@ function Get-NewOfficalPackage
}
}
+##############################
+#.SYNOPSIS
+# Returns True if NewVersion is newer than Version
+# Pre release are ignored if the current version is not pre-release
+# If the current version is pre-release, this function only determines if the version portion is NewReleaseTag
+# The calling function is responsible for sorting prelease version by publish date (as find-package gives them to you)
+# and returning the newest.
+#
+#.PARAMETER Version
+# The current Version
+#
+#.PARAMETER NewVersion
+# The potention replacement version
+#
+#.OUTPUTS
+# True if NewVersion should be considere as a replacement
+##############################
+function Test-IncludePackageVersion
+{
+ param(
+ [string]
+ $NewVersion,
+ [string]
+ $Version
+ )
+
+ $simpleCompare = $Version -notlike '*-*'
+
+ if($simpleCompare -and $NewVersion -like '*-*')
+ {
+ # We are using a stable and the new version is pre-release
+ return $false
+ }
+ elseif($simpleCompare -and [Version]$NewVersion -ge [Version] $Version)
+ {
+ # Simple comparison says the new version is newer
+ return $true
+ }
+ elseif($simpleCompare)
+ {
+ # Simple comparison was done, but it was not newer
+ return $false
+ }
+ elseif($NewVersion -notlike '*-*')
+ {
+ # Our current version is a pre-release but the new is not
+ # make sure the new version is newer than the version part of the current version
+ $versionOnly = ($Version -Split '\-')[0]
+ if([Version]$NewVersion -ge [Version] $versionOnly)
+ {
+ return $true
+ }
+ else
+ {
+ return $false
+ }
+ }
+ else
+ {
+ # Not sure, include it
+ return $true
+ }
+}
+
+##############################
+#.SYNOPSIS
+# Get a RegEx based on a version that will match the major and minor
+#
+#.PARAMETER Version
+# The version to match
+#
+##############################
+function Get-MatchingMajorMinorRegEx
+{
+ param(
+ [Parameter(Mandatory)]
+ $Version
+ )
+
+ $parts = $Version -split '\.'
+
+ $regEx = "^$($parts[0])\.$($parts[1])\..*"
+ return $regEx
+}
+
##############################
#.SYNOPSIS
# Update the version number in code