diff --git a/src/code/LocalServerApiCalls.cs b/src/code/LocalServerApiCalls.cs index c32f3db57..484ed351b 100644 --- a/src/code/LocalServerApiCalls.cs +++ b/src/code/LocalServerApiCalls.cs @@ -649,9 +649,10 @@ private Hashtable GetMetadataFromNupkg(string packageName, string packagePath, s _cmdletPassedIn.WriteDebug($"Extracting '{zipFilePath}' to '{tempDiscoveryPath}'"); System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempDiscoveryPath); - string psd1FilePath = Path.Combine(tempDiscoveryPath, $"{packageName}.psd1"); - string ps1FilePath = Path.Combine(tempDiscoveryPath, $"{packageName}.ps1"); - string nuspecFilePath = Path.Combine(tempDiscoveryPath, $"{packageName}.nuspec"); + string psd1FilePath = String.Empty; + string ps1FilePath = String.Empty; + string nuspecFilePath = String.Empty; + Utils.GetMetadataFilesFromPath(tempDiscoveryPath, packageName, out psd1FilePath, out ps1FilePath, out nuspecFilePath); List pkgTags = new List(); diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 2227b5e2a..769329d84 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -1,4 +1,3 @@ -using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -15,13 +14,15 @@ using System.Runtime.InteropServices; using Microsoft.PowerShell.Commands; using Microsoft.PowerShell.PSResourceGet.Cmdlets; +using System.Net; using System.Net.Http; using System.Globalization; using System.Security; using Azure.Core; using Azure.Identity; -using System.Threading.Tasks; +using System.Text.RegularExpressions; using System.Threading; +using System.Threading.Tasks; namespace Microsoft.PowerShell.PSResourceGet.UtilClasses { @@ -1171,6 +1172,34 @@ internal static HashSet GetInstalledPackages(List pathsToSearch, return pkgsInstalledOnMachine; } + internal static void GetMetadataFilesFromPath(string dirPath, string packageName, out string psd1FilePath, out string ps1FilePath, out string nuspecFilePath) + { + psd1FilePath = String.Empty; + ps1FilePath = String.Empty; + nuspecFilePath = String.Empty; + + var discoveredFiles = Directory.GetFiles(dirPath, "*.*", SearchOption.AllDirectories); + string pkgNamePattern = $"{packageName}*"; + Regex rgx = new(pkgNamePattern, RegexOptions.IgnoreCase); + foreach (var file in discoveredFiles) + { + if (rgx.IsMatch(file)) + { + if (file.EndsWith("psd1")) + { + psd1FilePath = file; + } + else if (file.EndsWith("nuspec")) + { + nuspecFilePath = file; + } + else if (file.EndsWith("ps1")) + { + ps1FilePath = file; + } + } + } + } #endregion #region PSDataFile parsing diff --git a/src/code/V2ResponseUtil.cs b/src/code/V2ResponseUtil.cs index 68dd1c9c2..4025f0c83 100644 --- a/src/code/V2ResponseUtil.cs +++ b/src/code/V2ResponseUtil.cs @@ -2,8 +2,10 @@ // Licensed under the MIT License. using Microsoft.PowerShell.PSResourceGet.UtilClasses; +using NuGet.Versioning; using System; using System.Collections.Generic; +using System.Linq; using System.Xml; namespace Microsoft.PowerShell.PSResourceGet.Cmdlets @@ -70,6 +72,10 @@ public override IEnumerable ConvertToPSResourceResult(FindResu #region V2 Specific Methods public XmlNode[] ConvertResponseToXML(string httpResponse) { + NuGetVersion emptyVersion = new NuGetVersion("0.0.0.0"); + NuGetVersion firstVersion = emptyVersion; + NuGetVersion lastVersion = emptyVersion; + bool shouldFixOrder = true; //Create the XmlDocument. XmlDocument doc = new XmlDocument(); @@ -80,7 +86,50 @@ public XmlNode[] ConvertResponseToXML(string httpResponse) { XmlNode[] nodes = new XmlNode[entryNode.Count]; for (int i = 0; i < entryNode.Count; i++) { - nodes[i] = entryNode[i]; + XmlNode node = entryNode[i]; + nodes[i] = node; + var entryChildNodes = node.ChildNodes; + foreach (XmlElement childNode in entryChildNodes) + { + var entryKey = childNode.LocalName; + if (entryKey.Equals("properties")) + { + var propertyChildNodes = childNode.ChildNodes; + foreach (XmlElement propertyChild in propertyChildNodes) + { + var propertyKey = propertyChild.LocalName; + var propertyValue = propertyChild.InnerText; + if (propertyKey.Equals("NormalizedVersion")) + { + if (!NuGetVersion.TryParse(propertyValue, out NuGetVersion parsedNormalizedVersion)) + { + // if a version couldn't be parsed, keep ordering as is. + shouldFixOrder = false; + } + + if (i == 0) + { + firstVersion = parsedNormalizedVersion; + } + else + { + // later version element + lastVersion = parsedNormalizedVersion; + } + + break; // don't care about rest of the childNode's properties + } + } + + break; // don't care about rest of the childNode's keys + } + } + } + + // order the array in descending order if not already. + if (shouldFixOrder && firstVersion.CompareTo(lastVersion) < 0) + { + nodes = nodes.Reverse().ToArray(); } return nodes; diff --git a/src/code/V2ServerAPICalls.cs b/src/code/V2ServerAPICalls.cs index 5713cfc0c..59b1a3ab4 100644 --- a/src/code/V2ServerAPICalls.cs +++ b/src/code/V2ServerAPICalls.cs @@ -364,6 +364,17 @@ public override FindResults FindName(string packageName, bool includePrerelease, string response = HttpRequestCall(requestUrlV2, out errRecord); if (errRecord != null) { + // usually this is for errors in calling the V2 server, but for ADO V2 this error will include package not found errors which we want to deliver in a standard message + if (_isADORepo && errRecord.Exception is ResourceNotFoundException) + { + errRecord = new ErrorRecord( + new ResourceNotFoundException($"Package with name '{packageName}' could not be found in repository '{Repository.Name}'. For ADO feed, if the package is in an upstream feed make sure you are authenticated to the upstream feed.", errRecord.Exception), + "PackageNotFound", + ErrorCategory.ObjectNotFound, + this); + response = string.Empty; + } + return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v2FindResponseType); } @@ -648,6 +659,17 @@ public override FindResults FindVersion(string packageName, string version, Reso string response = HttpRequestCall(requestUrlV2, out errRecord); if (errRecord != null) { + // usually this is for errors in calling the V2 server, but for ADO V2 this error will include package not found errors which we want to deliver with a standard message + if (_isADORepo && errRecord.Exception is ResourceNotFoundException) + { + errRecord = new ErrorRecord( + new ResourceNotFoundException($"Package with name '{packageName}' and version '{version}' could not be found in repository '{Repository.Name}'. For ADO feed, if the package is in an upstream feed make sure you are authenticated to the upstream feed.", errRecord.Exception), + "PackageNotFound", + ErrorCategory.ObjectNotFound, + this); + response = string.Empty; + } + return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v2FindResponseType); } diff --git a/test/FindPSResourceTests/FindPSResourceADOV2Server.Tests.ps1 b/test/FindPSResourceTests/FindPSResourceADOV2Server.Tests.ps1 index 9d4e3f8db..253dad68e 100644 --- a/test/FindPSResourceTests/FindPSResourceADOV2Server.Tests.ps1 +++ b/test/FindPSResourceTests/FindPSResourceADOV2Server.Tests.ps1 @@ -31,7 +31,7 @@ Describe 'Test HTTP Find-PSResource for ADO V2 Server Protocol' -tags 'CI' { $res = Find-PSResource -Name NonExistantModule -Repository $ADOV2RepoName -ErrorVariable err -ErrorAction SilentlyContinue $res | Should -BeNullOrEmpty $err.Count | Should -BeGreaterThan 0 - $err[0].FullyQualifiedErrorId | Should -BeExactly "ResourceNotFound,Microsoft.PowerShell.PSResourceGet.Cmdlets.FindPSResource" + $err[0].FullyQualifiedErrorId | Should -BeExactly "PackageNotFound,Microsoft.PowerShell.PSResourceGet.Cmdlets.FindPSResource" $res | Should -BeNullOrEmpty } diff --git a/test/InstallPSResourceTests/InstallPSResourceADOV2Server.Tests.ps1 b/test/InstallPSResourceTests/InstallPSResourceADOV2Server.Tests.ps1 index 3d53fb8c8..589bff92b 100644 --- a/test/InstallPSResourceTests/InstallPSResourceADOV2Server.Tests.ps1 +++ b/test/InstallPSResourceTests/InstallPSResourceADOV2Server.Tests.ps1 @@ -132,13 +132,14 @@ Describe 'Test Install-PSResource for V3Server scenarios' -tags 'CI' { $pkg.Version | Should -Be "5.0.0" } - It "Install resource with companyname and repository source location and validate properties" { + It "Install resource with author and repository source location and validate properties" { + # CompanyName is not present in ADO V2 feed response properties. Install-PSResource -Name $testModuleName -Version "5.2.5-alpha001" -Repository $ADORepoName -TrustRepository $pkg = Get-InstalledPSResource $testModuleName $pkg.Version | Should -Be "5.2.5" $pkg.Prerelease | Should -Be "alpha001" - $pkg.CompanyName | Should -Be "None" + $pkg.Author | Should -Be "None" $pkg.RepositorySourceLocation | Should -Be $ADORepoUri } diff --git a/test/InstallPSResourceTests/InstallPSResourceLocal.Tests.ps1 b/test/InstallPSResourceTests/InstallPSResourceLocal.Tests.ps1 index 493495282..437fcd1ad 100644 --- a/test/InstallPSResourceTests/InstallPSResourceLocal.Tests.ps1 +++ b/test/InstallPSResourceTests/InstallPSResourceLocal.Tests.ps1 @@ -16,15 +16,14 @@ Describe 'Test Install-PSResource for local repositories' -tags 'CI' { BeforeAll { $localRepo = "psgettestlocal" $localUNCRepo = "psgettestlocal3" - $localNupkgRepo = "LocalNupkgRepo" - $localNupkgRepoUri = "test\testFiles\testNupkgs" + $localNupkgRepo = "localNupkgRepo" $testModuleName = "test_local_mod" $testModuleName2 = "test_local_mod2" $testModuleClobber = "testModuleClobber" $testModuleClobber2 = "testModuleClobber2" Get-NewPSResourceRepositoryFile Register-LocalRepos - Register-PSResourceRepository -Name $localNupkgRepo -SourceLocation $localNupkgRepoUri + Register-LocalTestNupkgsRepo $prereleaseLabel = "alpha001" $tags = @() @@ -290,7 +289,11 @@ Describe 'Test Install-PSResource for local repositories' -tags 'CI' { It "Install .nupkg that contains directories (specific package throws errors when accessed by ZipFile.OpenRead)" { $nupkgName = "Microsoft.Web.Webview2" $nupkgVersion = "1.0.2792.45" - Install-PSResource -Name $nupkgName -Version $nupkgVersion -Repository $localNupkgRepo -TrustRepository + $repoPath = Get-PSResourceRepository $localNupkgRepo + Write-Verbose -Verbose "repoPath $($repoPath.Uri)" + $searchPkg = Find-PSResource -Name $nupkgName -Version $nupkgVersion -Repository $localNupkgRepo + Write-Verbose -Verbose "search name: $($searchPkg.Name)" + Install-PSResource -Name $nupkgName -Version $nupkgVersion -Repository $localNupkgRepo -TrustRepository -Verbose $pkg = Get-InstalledPSResource $nupkgName $pkg.Name | Should -Be $nupkgName $pkg.Version | Should -Be $nupkgVersion diff --git a/test/PSGetTestUtils.psm1 b/test/PSGetTestUtils.psm1 index c9f5006b4..6a384c17c 100644 --- a/test/PSGetTestUtils.psm1 +++ b/test/PSGetTestUtils.psm1 @@ -273,6 +273,26 @@ function Register-LocalRepos { Write-Verbose "registered psgettestlocal, psgettestlocal2, psgettestlocal3, psgettestlocal4" } +function Register-LocalTestNupkgsRepo { + # Path to folder, within our test folder, where we store special case modules, scripts and nupkgs used for testing + $testDir = (get-item $psscriptroot).FullName + $testFilesFolderPath = Join-Path $testDir -ChildPath "testFiles" + + # Path to specifically to that invalid test nupkgs folder + $testNupkgsFolderPath = Join-Path $testFilesFolderPath -ChildPath "testNupkgs" + Write-Verbose -Verbose "testNupkgsFolderPath: $testNupkgsFolderPath" + + $repoUriAddress = $testNupkgsFolderPath + $localRepoParams = @{ + Name = "localNupkgRepo" + Uri = $repoUriAddress + Priority = 70 + Trusted = $false + } + + Register-PSResourceRepository @localRepoParams +} + function Register-PSGallery { $PSGalleryRepoParams = @{ Name = $script:PSGalleryName