src/SavePSResource.cs

 
using System;
using System.Collections;
using System.Management.Automation;
using System.Collections.Generic;
using NuGet.Configuration;
using NuGet.Common;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using System.Threading;
using NuGet.Packaging.Core;
using NuGet.Versioning;
using System.Net;
using System.Linq;
using MoreLinq.Extensions;
using System.IO;
using Microsoft.PowerShell.PowerShellGet.RepositorySettings;
using System.Globalization;
using System.Security.Principal;
using static System.Environment;
 
namespace Microsoft.PowerShell.PowerShellGet.Cmdlets
{
 
 
    /// <summary>
    /// The Save-PSResource cmdlet saves a resource, either packed or unpacked.
    /// It returns nothing.
    /// </summary>
 
    [Cmdlet(VerbsData.Save, "PSResource", DefaultParameterSetName = "NameParameterSet", SupportsShouldProcess = true,
    HelpUri = "<add>", RemotingCapability = RemotingCapability.None)]
    public sealed
    class SavePSResource : PSCmdlet
    {
        /// <summary>
        /// Specifies the exact names of resources to save from a repository.
        /// A comma-separated list of module names is accepted. The resource name must match the resource name in the repository.
        /// </summary>
        [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "NameParameterSet")]
        [ValidateNotNullOrEmpty]
        public string[] Name
        {
            get
            { return _name; }
 
            set
            { _name = value; }
        }
        private string[] _name; // = new string[0];
 
        /// <summary>
        /// Specifies the version or version range of the package to be saved
        /// </summary>
        [Parameter(ParameterSetName = "NameParameterSet")]
        [ValidateNotNullOrEmpty]
        public string Version
        {
            get
            { return _version; }
 
            set
            { _version = value; }
        }
        private string _version;
 
        /// <summary>
        /// Specifies to allow installation of prerelease versions
        /// </summary>
        [Parameter(ParameterSetName = "NameParameterSet")]
        public SwitchParameter Prerelease
        {
            get
            { return _prerelease; }
 
            set
            { _prerelease = value; }
        }
        private SwitchParameter _prerelease;
 
 
        /// <summary>
        /// Specifies a user account that has rights to find a resource from a specific repository.
        /// </summary>
        [Parameter(ParameterSetName = "NameParameterSet")]
        [ValidateNotNullOrEmpty]
        public string[] Repository
        {
            get
            { return _repository; }
 
            set
            { _repository = value; }
        }
        private string[] _repository;
      
        /// <summary>
        /// Specifies a user account that has rights to find a resource from a specific repository.
        /// </summary>
        [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "NameParameterSet")]
        public PSCredential Credential
        {
            get
            { return _credential; }
 
            set
            { _credential = value; }
        }
        private PSCredential _credential;
 
        /// <summary>
        /// Saves as a .nupkg
        /// </summary>
        [Parameter()]
        public SwitchParameter AsNupkg
        {
            get { return _asNupkg; }
 
            set { _asNupkg = value; }
        }
        private SwitchParameter _asNupkg;
 
        /// <summary>
        /// Saves the metadata XML file with the resource
        /// </summary>
        [Parameter()]
        public SwitchParameter IncludeXML
        {
            get { return _includeXML; }
 
            set { _includeXML = value; }
        }
        private SwitchParameter _includeXML;
 
        /// <summary>
        /// The destination where the resource is to be installed. Works for all resource types.
        /// </summary>
        [Parameter(ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "NameParameterSet")]
        [ValidateNotNullOrEmpty]
        public string Path
        {
            get
            { return _path; }
 
            set
            { _path = value; }
        }
        private string _path;
 
        /*
        /// <summary>
        /// For modules that require a license, AcceptLicense automatically accepts the license agreement during installation.
        /// </summary>
        [Parameter()]
        public SwitchParameter AcceptLicense
        {
            get { return _acceptLicense; }
 
            set { _acceptLicense = value; }
        }
        private SwitchParameter _acceptLicense;
        */
 
 
        // This will be a list of all the repository caches
        public static readonly List<string> RepoCacheFileName = new List<string>();
        public static readonly string RepositoryCacheDir = System.IO.Path.Combine(Environment.GetFolderPath(SpecialFolder.LocalApplicationData), "PowerShellGet", "RepositoryCache");
        public static readonly string OsPlatform = System.Runtime.InteropServices.RuntimeInformation.OSDescription;
 
        private List<string> pkgsLeftToInstall;
        // Define the cancellation token.
        CancellationTokenSource source;
        CancellationToken cancellationToken;
 
 
 
 
        /// <summary>
        /// </summary>
        protected override void ProcessRecord()
        {
            source = new CancellationTokenSource();
            cancellationToken = source.Token;
 
            var r = new RespositorySettings();
            var listOfRepositories = r.Read(_repository);
 
 
            //if (string.Equals(listOfRepositories[0].Properties["Trusted"].Value.ToString(), "false", StringComparison.InvariantCultureIgnoreCase) && !_trustRepository && !_force)
            //{
                // throw error saying repository is not trusted
                // throw new System.ArgumentException(string.Format(CultureInfo.InvariantCulture, "This repository is not trusted")); /// we should prompt for user input to accept
 
                /*
                 * Untrusted repository
                 * You are installing the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSResourceRepository cmdlet.
                 * Are you sure you want to install the modules from '<repo name >'?
                 * [Y] Yes [A] Yes to ALl [N] No [L] No to all [s] suspendd [?] Help (default is "N"):
                 */
           // }
 
 
            pkgsLeftToInstall = _name.ToList();
            foreach (var repoName in listOfRepositories)
            {
                // if it can't find the pkg in one repository, it'll look in the next one in the list
                // returns any pkgs that weren't found
                var returnedPkgsNotInstalled = InstallHelper(repoName.Properties["Url"].Value.ToString(), pkgsLeftToInstall, cancellationToken);
                if (!pkgsLeftToInstall.Any())
                {
                    return;
                }
                pkgsLeftToInstall = returnedPkgsNotInstalled;
            }
        }
 
 
 
 
 
        public List<string> InstallHelper(string repositoryUrl, List<string> pkgsLeftToInstall, CancellationToken cancellationToken)
        {
            PackageSource source = new PackageSource(repositoryUrl);
 
            if (_credential != null)
            {
                string password = new NetworkCredential(string.Empty, _credential.Password).Password;
                source.Credentials = PackageSourceCredential.FromUserInput(repositoryUrl, _credential.UserName, password, true, null);
            }
 
            var provider = FactoryExtensionsV3.GetCoreV3(NuGet.Protocol.Core.Types.Repository.Provider);
 
            SourceRepository repository = new SourceRepository(source, provider);
 
            SearchFilter filter = new SearchFilter(_prerelease);
 
 
 
            ////////////////////// packages from source
            ///
            PackageSource source2 = new PackageSource(repositoryUrl);
            if (_credential != null)
            {
                string password = new NetworkCredential(string.Empty, _credential.Password).Password;
                source2.Credentials = PackageSourceCredential.FromUserInput(repositoryUrl, _credential.UserName, password, true, null);
            }
            var provider2 = FactoryExtensionsV3.GetCoreV3(NuGet.Protocol.Core.Types.Repository.Provider);
 
            SourceRepository repository2 = new SourceRepository(source2, provider2);
            // TODO: proper error handling here
            PackageMetadataResource resourceMetadata2 = null;
            try
            {
                resourceMetadata2 = repository.GetResourceAsync<PackageMetadataResource>().GetAwaiter().GetResult();
            }
            catch
            { }
 
            SearchFilter filter2 = new SearchFilter(_prerelease);
            SourceCacheContext context2 = new SourceCacheContext();
 
 
 
            foreach (var n in _name)
            {
               
 
                IPackageSearchMetadata filteredFoundPkgs = null;
 
                // Check version first to narrow down the number of pkgs before potential searching through tags
                VersionRange versionRange = null;
                if (_version == null)
                {
                    // ensure that the latst version is returned first (the ordering of versions differ
                    // TODO: proper error handling
                    try
                    {
                        filteredFoundPkgs = (resourceMetadata2.GetMetadataAsync(n, _prerelease, false, context2, NullLogger.Instance, cancellationToken).GetAwaiter().GetResult()
                            .OrderByDescending(p => p.Identity.Version, VersionComparer.VersionRelease)
                            .FirstOrDefault());
                    }
                    catch { }
                }
                else
                {
                    // check if exact version
                    NuGetVersion nugetVersion;
 
                    //VersionRange versionRange = VersionRange.Parse(version);
                    NuGetVersion.TryParse(_version, out nugetVersion);
                    // throw
 
                    if (nugetVersion != null)
                    {
                        // exact version
                        versionRange = new VersionRange(nugetVersion, true, nugetVersion, true, null, null);
                    }
                    else
                    {
                        // check if version range
                        versionRange = VersionRange.Parse(_version);
                    }
 
 
                    // Search for packages within a version range
                    // ensure that the latst version is returned first (the ordering of versions differ
                    filteredFoundPkgs = (resourceMetadata2.GetMetadataAsync(n, _prerelease, false, context2, NullLogger.Instance, cancellationToken).GetAwaiter().GetResult()
                        .Where(p => versionRange.Satisfies(p.Identity.Version))
                        .OrderByDescending(p => p.Identity.Version, VersionComparer.VersionRelease)
                        .FirstOrDefault());
 
                }
 
 
                List<IPackageSearchMetadata> foundDependencies = new List<IPackageSearchMetadata>();
 
 
                // found a package to install and looking for dependencies
                // Search for dependencies
                if (filteredFoundPkgs != null)
                {
                    // need to parse the depenency and version and such
 
                    // need to improve this later
                    // this function recursively finds all dependencies
                    // might need to do add instead of AddRange
                    foundDependencies.AddRange(FindDependenciesFromSource(filteredFoundPkgs, resourceMetadata2, context2));
 
 
                } /// end dep conditional
 
 
                // check which pkgs you actually need to install
 
                List<IPackageSearchMetadata> pkgsToInstall = new List<IPackageSearchMetadata>();
                // install pkg, then install any dependencies to a temp directory
 
                pkgsToInstall.Add(filteredFoundPkgs);
                pkgsToInstall.AddRange(foundDependencies);
 
 
 
 
                if (_asNupkg)
                {
                    // CreateFolderFeedV3Async(_path, PackageSaveMode.Nupkg | PackageSaveMode.Nuspec, packages).
                    var tempInstallPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString());
                    var dir = Directory.CreateDirectory(tempInstallPath); // should check it gets created properly
                    //dir.SetAccessControl(new DirectorySecurity(dir.FullName, AccessControlSections.Owner));
                    // To delete file attributes from the existing ones get the current file attributes first and use AND (&) operator
                    // with a mask (bitwise complement of desired attributes combination).
                    dir.Attributes = dir.Attributes & ~FileAttributes.ReadOnly;
 
                    //remove any null pkgs
                    pkgsToInstall.Remove(null);
 
                    // install everything to a temp path
                    foreach (var p in pkgsToInstall)
                    {
                        var pkgIdentity = new PackageIdentity(p.Identity.Id, p.Identity.Version);
 
 
                        var resource = new DownloadResourceV2FeedProvider();
                        var resource2 = resource.TryCreate(repository, cancellationToken);
 
 
                        var cacheContext = new SourceCacheContext();
 
 
                        var downloadResource = repository.GetResourceAsync<DownloadResource>().GetAwaiter().GetResult();
 
 
                        var result = downloadResource.GetDownloadResourceResultAsync(
                            pkgIdentity,
                            new PackageDownloadContext(cacheContext),
                            tempInstallPath,
                            logger: NullLogger.Instance,
                            CancellationToken.None).GetAwaiter().GetResult();
 
                        // need to close the .nupkg
                        result.Dispose();
 
                        // 4) copy to proper path
                        // TODO: test installing a script when it already exists
                        // or move to script path
                        // check for failures
                        // var newPath = Directory.CreateDirectory(Path.Combine(psModulesPath, p.Identity.Id, p.Identity.Version.ToNormalizedString()));
 
                        var installPath = SessionState.Path.GetResolvedPSPathFromPSPath(_path).FirstOrDefault().Path;
                        // when we move the directory over, we'll change the casing of the module directory name from lower case to proper casing.
                        // if script, just move the files over, if module, move the version directory overp
 
                        var tempPkgIdPath = System.IO.Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToString());
                        var tempPkgVersionPath = System.IO.Path.Combine(tempPkgIdPath, p.Identity.Id.ToLower() + "." + p.Identity.Version + ".nupkg");
 
                        var newPath = System.IO.Path.Combine(installPath, p.Identity.Id + "." + p.Identity.Version + ".nupkg");
 
                        File.Move(tempPkgVersionPath, newPath);
 
                        // 2) TODO: Verify that all the proper modules installed correctly
                        // remove temp directory recursively
                        Directory.Delete(tempInstallPath, true);
 
                        pkgsLeftToInstall.Remove(n);
 
                    }
 
                }
                else
                {
                    var tempInstallPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString());
                    var dir = Directory.CreateDirectory(tempInstallPath); // should check it gets created properly
                    //dir.SetAccessControl(new DirectorySecurity(dir.FullName, AccessControlSections.Owner));
                    // To delete file attributes from the existing ones get the current file attributes first and use AND (&) operator
                    // with a mask (bitwise complement of desired attributes combination).
                    dir.Attributes = dir.Attributes & ~FileAttributes.ReadOnly;
 
                    //remove any null pkgs
                    pkgsToInstall.Remove(null);
 
                    // install everything to a temp path
                    foreach (var p in pkgsToInstall)
                    {
                        var pkgIdentity = new PackageIdentity(p.Identity.Id, p.Identity.Version);
 
 
                        var resource = new DownloadResourceV2FeedProvider();
                        var resource2 = resource.TryCreate(repository, cancellationToken);
 
 
                        var cacheContext = new SourceCacheContext();
 
 
                        var downloadResource = repository.GetResourceAsync<DownloadResource>().GetAwaiter().GetResult();
 
 
                        var result = downloadResource.GetDownloadResourceResultAsync(
                            pkgIdentity,
                            new PackageDownloadContext(cacheContext),
                            tempInstallPath,
                            logger: NullLogger.Instance,
                            CancellationToken.None).GetAwaiter().GetResult();
 
                        // need to close the .nupkg
                        result.Dispose();
 
 
                        // create a download count to see if everything was installed properly
 
                        // 1) remove the *.nupkg file
 
                        // may need to modify due to capitalization
                        var dirNameVersion = System.IO.Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToNormalizedString());
                        var nupkgMetadataToDelete = System.IO.Path.Combine(dirNameVersion, ".nupkg.metadata").ToLower();
                        var nupkgToDelete = System.IO.Path.Combine(dirNameVersion, (p.Identity.ToString() + ".nupkg").ToLower());
                        var nupkgSHAToDelete = System.IO.Path.Combine(dirNameVersion, (p.Identity.ToString() + ".nupkg.sha512").ToLower());
                        var nuspecToDelete = System.IO.Path.Combine(dirNameVersion, (p.Identity.Id + ".nuspec").ToLower());
 
 
                        File.Delete(nupkgMetadataToDelete);
                        File.Delete(nupkgSHAToDelete);
                        File.Delete(nuspecToDelete);
                        File.Delete(nupkgToDelete);
 
 
                        // if it's not a script, do the following:
                        var scriptPath = System.IO.Path.Combine(dirNameVersion, (p.Identity.Id.ToString() + ".ps1").ToLower());
                        var isScript = File.Exists(scriptPath) ? true : false;
 
                        // 3) create xml
                        //Create PSGetModuleInfo.xml
                        //Set attribute as hidden [System.IO.File]::SetAttributes($psgetItemInfopath, [System.IO.FileAttributes]::Hidden)
 
 
 
                        var fullinstallPath = isScript ? System.IO.Path.Combine(dirNameVersion, (p.Identity.Id + "_InstalledScriptInfo.xml"))
                            : System.IO.Path.Combine(dirNameVersion, "PSGetModuleInfo.xml");
 
                        if (_includeXML)
                        {
                            // Create XMLs
                            using (StreamWriter sw = new StreamWriter(fullinstallPath))
                            {
                                var psModule = "PSModule";
 
                                var tags = p.Tags.Split(' ');
 
 
                                var module = tags.Contains("PSModule") ? "Module" : null;
                                var script = tags.Contains("PSScript") ? "Script" : null;
 
 
                                List<string> includesDscResource = new List<string>();
                                List<string> includesCommand = new List<string>();
                                List<string> includesFunction = new List<string>();
                                List<string> includesRoleCapability = new List<string>();
                                List<string> filteredTags = new List<string>();
 
                                var psDscResource = "PSDscResource_";
                                var psCommand = "PSCommand_";
                                var psFunction = "PSFunction_";
                                var psRoleCapability = "PSRoleCapability_";
 
 
 
                                foreach (var tag in tags)
                                {
                                    if (tag.StartsWith(psDscResource))
                                    {
                                        includesDscResource.Add(tag.Remove(0, psDscResource.Length));
                                    }
                                    else if (tag.StartsWith(psCommand))
                                    {
                                        includesCommand.Add(tag.Remove(0, psCommand.Length));
                                    }
                                    else if (tag.StartsWith(psFunction))
                                    {
                                        includesFunction.Add(tag.Remove(0, psFunction.Length));
                                    }
                                    else if (tag.StartsWith(psRoleCapability))
                                    {
                                        includesRoleCapability.Add(tag.Remove(0, psRoleCapability.Length));
                                    }
                                    else if (!tag.StartsWith("PSWorkflow_") && !tag.StartsWith("PSCmdlet_") && !tag.StartsWith("PSIncludes_")
                                        && !tag.Equals("PSModule") && !tag.Equals("PSScript"))
                                    {
                                        filteredTags.Add(tag);
                                    }
                                }
 
                                Dictionary<string, List<string>> includes = new Dictionary<string, List<string>>() {
                                { "DscResource", includesDscResource },
                                { "Command", includesCommand },
                                { "Function", includesFunction },
                                { "RoleCapability", includesRoleCapability }
                            };
 
 
                                Dictionary<string, VersionRange> dependencies = new Dictionary<string, VersionRange>();
                                foreach (var depGroup in p.DependencySets)
                                {
                                    PackageDependency depPkg = depGroup.Packages.FirstOrDefault();
                                    dependencies.Add(depPkg.Id, depPkg.VersionRange);
                                }
 
 
                                var psGetModuleInfoObj = new PSObject();
                                // TODO: Add release notes
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("Name", p.Identity.Id));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("Version", p.Identity.Version));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("Type", module != null ? module : (script != null ? script : null)));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("Description", p.Description));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("Author", p.Authors));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("CompanyName", p.Owners));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("PublishedDate", p.Published));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("InstalledDate", System.DateTime.Now));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("LicenseUri", p.LicenseUrl));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("ProjectUri", p.ProjectUrl));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("IconUri", p.IconUrl));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("Includes", includes.ToList())); // TODO: check if getting deserialized properly
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("PowerShellGetFormatVersion", "3"));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("Dependencies", dependencies.ToList()));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("RepositorySourceLocation", repositoryUrl));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("Repository", repositoryUrl));
                                psGetModuleInfoObj.Members.Add(new PSNoteProperty("InstalledLocation", null)); // TODO: add installation location
 
 
 
                                psGetModuleInfoObj.TypeNames.Add("Microsoft.PowerShell.Commands.PSRepositoryItemInfo");
 
 
                                var serializedObj = PSSerializer.Serialize(psGetModuleInfoObj);
 
 
                                sw.Write(serializedObj);
 
                                // set the xml attribute to hidden
                                //System.IO.File.SetAttributes("c:\\code\\temp\\installtestpath\\PSGetModuleInfo.xml", FileAttributes.Hidden);
                            }
                        }
 
 
                        // 4) copy to proper path
 
                        // TODO: test installing a script when it already exists
                        // or move to script path
                        // check for failures
                        // var newPath = Directory.CreateDirectory(Path.Combine(psModulesPath, p.Identity.Id, p.Identity.Version.ToNormalizedString()));
 
                        var installPath = SessionState.Path.GetResolvedPSPathFromPSPath(_path).FirstOrDefault().Path;
                        var newPath = isScript ? installPath
                            : System.IO.Path.Combine(installPath, p.Identity.Id.ToString());
                        // when we move the directory over, we'll change the casing of the module directory name from lower case to proper casing.
 
                        // if script, just move the files over, if module, move the version directory overp
                        var tempModuleVersionDir = isScript ? System.IO.Path.Combine(tempInstallPath, p.Identity.Id.ToLower(), p.Identity.Version.ToNormalizedString())
                            : System.IO.Path.Combine(tempInstallPath, p.Identity.Id.ToLower());
 
                        if (isScript)
                        {
                            var scriptXML = p.Identity.Id + "_InstalledScriptInfo.xml";
                            File.Move(System.IO.Path.Combine(tempModuleVersionDir, scriptXML), System.IO.Path.Combine(installPath, "InstalledScriptInfos", scriptXML));
                            File.Move(System.IO.Path.Combine(tempModuleVersionDir, p.Identity.Id.ToLower() + ".ps1"), System.IO.Path.Combine(newPath, p.Identity.Id + ".ps1"));
                        }
                        else
                        {
                            if (!Directory.Exists(newPath))
                            {
                                Directory.Move(tempModuleVersionDir, newPath);
                            }
                            else
                            {
                                // If the module directory path already exists, Directory.Move throws an exception, so we'll just move the version directory over instead
                                tempModuleVersionDir = System.IO.Path.Combine(tempModuleVersionDir, p.Identity.Version.ToNormalizedString());
                                Directory.Move(tempModuleVersionDir, System.IO.Path.Combine(newPath, p.Identity.Version.ToNormalizedString()));
                            }
                        }
 
 
                        // 2) TODO: Verify that all the proper modules installed correctly
                        // remove temp directory recursively
                        Directory.Delete(tempInstallPath, true);
 
                        pkgsLeftToInstall.Remove(n);
                    }
                }
            }
 
            return pkgsLeftToInstall;
        }
 
 
 
        private List<IPackageSearchMetadata> FindDependenciesFromSource(IPackageSearchMetadata pkg, PackageMetadataResource pkgMetadataResource, SourceCacheContext srcContext)
        {
            /// dependency resolver
            ///
            /// this function will be recursively called
            ///
            /// call the findpackages from source helper (potentially generalize this so it's finding packages from source or cache)
            ///
            List<IPackageSearchMetadata> foundDependencies = new List<IPackageSearchMetadata>();
 
            // 1) check the dependencies of this pkg
            // 2) for each dependency group, search for the appropriate name and version
            // a dependency group are all the dependencies for a particular framework
            foreach (var dependencyGroup in pkg.DependencySets)
            {
 
                //dependencyGroup.TargetFramework
                //dependencyGroup.
 
                foreach (var pkgDependency in dependencyGroup.Packages)
                {
 
                    // 2.1) check that the appropriate pkg dependencies exist
                    // returns all versions from a single package id.
                    var dependencies = pkgMetadataResource.GetMetadataAsync(pkgDependency.Id, _prerelease, true, srcContext, NullLogger.Instance, cancellationToken).GetAwaiter().GetResult();
 
                    // then 2.2) check if the appropriate verion range exists (if version exists, then add it to the list to return)
 
                    VersionRange versionRange = null;
                    try
                    {
                        versionRange = VersionRange.Parse(pkgDependency.VersionRange.OriginalString);
                    }
                    catch
                    {
                        Console.WriteLine("Error parsing version range");
                    }
 
 
                    // if no version/version range is specified the we just return the latest version
 
                    IPackageSearchMetadata depPkgToReturn = (versionRange == null ?
                        dependencies.FirstOrDefault() :
                        dependencies.Where(v => versionRange.Satisfies(v.Identity.Version)).FirstOrDefault());
 
 
                    foundDependencies.Add(depPkgToReturn);
                    
                    // 3) search for any dependencies the pkg has
                    foundDependencies.AddRange(FindDependenciesFromSource(depPkgToReturn, pkgMetadataResource, srcContext));
                }
            }
 
 
            // flatten after returning
            return foundDependencies;
        }
 
 
 
 
 
 
 
 
 
 
 
 
    }
}