src/PublishPSResource.cs

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
 
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using NuGet.Configuration;
using NuGet.Commands;
using NuGet.Packaging;
using NuGet.Common;
using System;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Xml;
using System.Linq;
using NuGet.Versioning;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections;
using System.Runtime.Serialization;
 
namespace Microsoft.PowerShell.PowerShellGet.Cmdlets
{
    /// <summary>
    /// It retrieves a resource that was installEd with Install-PSResource
    /// Returns a single resource or multiple resource.
    /// </summary>
    [Cmdlet(VerbsData.Publish, "PSResource", SupportsShouldProcess = true,
        HelpUri = "<add>", RemotingCapability = RemotingCapability.None)]
    public sealed
    class PublishPSResource : PSCmdlet
    {
        /// <summary>
        /// Specifies the name of the resource to be published.
        /// </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;
 
 
        /// <summary>
        /// Specifies the API key that you want to use to publish a module to the online gallery.
        /// </summary>
        [Parameter()]
        [ValidateNotNullOrEmpty]
        public string APIKey
        {
            get
            { return _APIKey; }
 
            set
            { _APIKey = value; }
        }
        private string _APIKey;
 
 
        /// <summary>
        /// Specifies the repository to publish to.
        /// </summary>
        [Parameter()]
        [ValidateNotNullOrEmpty]
        public string Repository
        {
            get
            { return _repository; }
 
            set
            { _repository = value; }
        }
        private string _repository;
 
 
 
        /// <summary>
        /// Can be used to publish the a nupkg locally.
        /// </summary>
        [Parameter()]
        [ValidateNotNullOrEmpty]
        public string DestinationPath
        {
            get
            { return _destinationPath; }
 
            set
            { _destinationPath = value; }
        }
        private string _destinationPath;
 
 
 
        /// TODO
        /// <summary>
        /// Specifies the path to the resource that you want to publish. This parameter accepts the path to the folder that contains the resource.
        /// Specifies a path to one or more locations. Wildcards are permitted. The default location is the current directory (.).
        /// </summary>
        [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathsParameterSet")]
        [ValidateNotNullOrEmpty]
        public string Path
        {
            get
            { return _path; }
 
            set
            { _path = value; }
        }
        private string _path;
         
 
 
        /// TODO
        /// <summary>
        /// Specifies a path to one or more locations. Unlike the Path parameter, the value of the LiteralPath parameter is used exactly as entered.
        /// No characters are interpreted as wildcards. If the path includes escape characters, enclose them in single quotation marks.
        /// Single quotation marks tell PowerShell not to interpret any characters as escape sequences.
        /// </summary>
        [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "PathLiteralParameterSet")]
        [ValidateNotNullOrEmpty]
        // [Alias('PSPath')]
        public string LiteralPath
        {
         get
         { return _literalPath; }
 
         set
         { _literalPath = value; }
        }
        private string _literalPath;
 
 
        /// TODO
        /// <summary>
        /// Specifies the exact version of a single resource to publish.
        /// </summary>
        [Parameter(ParameterSetName = "NameParameterSet")]
        [ValidateNotNullOrEmpty]
        public string RequiredVersion
        {
            get
            { return _requiredVersion; }
 
            set
            { _requiredVersion = value; }
        }
        private string _requiredVersion;
 
 
        // TODO
        /// <summary>
        /// Allows resources marked as prerelease to be published.
        /// </summary>
        [Parameter(ParameterSetName = "NameParameterSet")]
        [ValidateNotNullOrEmpty]
        public SwitchParameter Prerelease
        {
            get
            { return _prerelease; }
 
            set
            { _prerelease = value; }
        }
        private bool _prerelease;
 
 
 
 
 
 
 
        /*
 
             # Specifies a user account that has rights to a specific repository. /// used for finding dependencies
             [Parameter(ValueFromPipelineByPropertyName = $true)]
             [PSCredential]
             $Credential,
 
 
 
             # Forces the command to run without asking for user confirmation.
             [Parameter()]
             [switch]
             $Force,
 
 
 
 
             # Bypasses the default check that all dependencies are present.
             [Parameter()]
             [switch]
             $SkipDependenciesCheck,
 
              
         */
 
        /// TODO: (should this be parsed form the psd1??) >>> consider removing
        /// <summary>
        /// Specifies a string containing release notes or comments that you want to be available to users of this version of the resource.
        /// Note-- this applies only to the nuspec.
        /// </summary>
        [Parameter()]
        [ValidateNotNullOrEmpty]
        public string ReleaseNotes
        {
            get
            { return _releaseNotes; }
 
            set
            { _releaseNotes = value; }
        }
        private string _releaseNotes;
 
 
 
 
        /// TODO: (should this be parsed form the psd1??) >>> consider removing
        /// <summary>
        /// Adds one or more tags to the resource that you are publishing.
        /// Note-- this applies only to the nuspec.
        /// </summary>
        [Parameter()]
        [ValidateNotNullOrEmpty]
        public string[] Tags
        {
            get
            { return _tags; }
 
            set
            { _tags = value; }
        }
        private string[] _tags;
 
 
 
 
 
 
        /// TODO: (should this be parsed form the psd1??) >>> consider removing
        /// <summary>
        /// Specifies the URL of licensing terms for the resource you want to publish.
        /// Note-- this applies only to the nuspec.
        /// </summary>
        [Parameter()]
        [ValidateNotNullOrEmpty]
        public string LicenseUrl
        {
            get
            { return _licenseUrl; }
 
            set
            { _licenseUrl = value; }
        }
        private string _licenseUrl;
 
 
 
 
        /// TODO: (should this be parsed form the psd1??) >>> consider removing
        /// <summary>
        /// Specifies the URL of an icon for the resource.
        /// Note-- this applies only to the nuspec.
        /// </summary>
        [Parameter()]
        [ValidateNotNullOrEmpty]
        public string IconUrl
        {
            get
            { return _iconUrl; }
 
            set
            { _iconUrl = value; }
        }
        private string _iconUrl;
 
 
        /// TODO: (should this be parsed form the psd1??) >>> consider removing
        /// <summary>
        /// Specifies the URL of a webpage about this project.
        /// Note-- this applies only to the nuspec.
        /// </summary>
        [Parameter()]
        [ValidateNotNullOrEmpty]
        public string ProjectUrl
        {
            get
            { return _projectUrl; }
 
            set
            { _projectUrl = value; }
        }
        private string _projectUrl;
 
 
 
 
 
 
 
 
 
        // done
        [Parameter(ParameterSetName = "ModuleNameParameterSet")]
        /// <summary>
        /// Excludes files from a nuspec
        /// </summary>
        [Parameter()]
        [ValidateNotNullOrEmpty]
        public string[] Exclude
        {
            get
            { return _exclude; }
 
            set
            { _exclude = value; }
        }
        private string[] _exclude = System.Array.Empty<string>();
 
        // done
        /// <summary>
        /// Specifies a nuspec file rather than relying on this module to produce one.
        /// </summary>
        [Parameter()]
        [ValidateNotNullOrEmpty]
        public string Nuspec
        {
            get
            { return _nuspec; }
 
            set
            { _nuspec = value; }
        }
        private string _nuspec;
 
 
 
 
 
 
        /// <summary>
        /// </summary>
        protected override void ProcessRecord()
        {
 
            string resolvedPath = "";
            if (!string.IsNullOrEmpty(_name))
            {
                // name parameterset
 
                // should return path to the module or script
                resolvedPath = publishNameParamSet();
 
            }
            else {
                // one of the paths parameter set
 
                // resolve path
 
                // this should be path:
                //"C:\\Users\\americks\\Desktop\\newpsgettestmodule.2.0.0", //"C:\\code\\TestPackage1",
                  
                if (!string.IsNullOrEmpty(_literalPath))
                {
                    resolvedPath = _literalPath;
                }
                else if (!string.IsNullOrEmpty(_path))
                {
                    resolvedPath = SessionState.Path.GetResolvedPSPathFromPSPath(_path).FirstOrDefault().Path;
 
                }
 
                if (string.IsNullOrEmpty(resolvedPath))
                {
                    //sThrowTerminatingError();
                }
 
            }
 
 
 
            // first do modules, then do scripts (could be module or script)
            // get the psd1 file or .ps1 file
            string dirName = new DirectoryInfo(resolvedPath).Name;
 
            string moduleFile = "";
            if (File.Exists(System.IO.Path.Combine(resolvedPath, dirName + ".psd1")))
            {
                // if a module
                moduleFile = System.IO.Path.Combine(resolvedPath, dirName + ".psd1");
            }
            else if (File.Exists(System.IO.Path.Combine(resolvedPath, dirName + ".ps1")))
            {
                // if a script
                moduleFile = System.IO.Path.Combine(resolvedPath, dirName + ".ps1");
            }
            else {
//ThrowTerminatingError(); // .psd1 or .ps1 does not exist
            }
 
 
 
 
            string outputDir = "";
            // if there's no specified destination path to publish the nupkg, we'll just create a temp folder and delete it later
            if (!string.IsNullOrEmpty(_destinationPath))
            {
                // check if there's an extenssion... the directory should be passed in
                outputDir = SessionState.Path.GetResolvedPSPathFromPSPath(_destinationPath).FirstOrDefault().Path;
            }
            else
            {
                // TODO: create temp directory
                outputDir = "C:\\code\\testdirectory";
            }
 
 
 
            // if user specifies they want to use a nuspec they've created
            if (string.IsNullOrEmpty(_nuspec))
            {
                _nuspec = createNuspec(outputDir, moduleFile, dirName);
                // check if nuspec gets created successfully
            }
 
 
 
 
 
 
 
 
            /////// PACK INTO A NUPKG GIVEN A NUSPEC
            var builder = new PackageBuilder();
                var runner = new PackCommandRunner(
                    new PackArgs
                    {
                        CurrentDirectory = resolvedPath,
                        OutputDirectory = outputDir, // make temp directory for this
                        Path = _nuspec, //"NewpsGetTestModule.nuspec",
                        Exclude = _exclude,
                        Symbols = false,
                        Logger = NullLogger.Instance
                    },
                    MSBuildProjectFactory.ProjectCreator,
                    builder);;
 
                runner.BuildPackage();
            ///// END PACK
 
 
 
            ////// PUSH
 
            var fullNupkgPath = System.IO.Path.Combine(outputDir, "newpsgettestmodule.2.1.0.nupkg");
            // check if nupkg successfully installed, if so continue on, if not throw error
 
 
            var settings = NuGet.Configuration.Settings.LoadDefaultSettings(null, null, null);
 
            ILogger log = new TestLogger();
            PushRunner.Run(
                    Settings.LoadDefaultSettings(null, null, null),
                    new PackageSourceProvider(settings),
                    fullNupkgPath, //packagePath ////packageInfo.FullName,
                    _repository, //"https://www.poshtestgallery.com/api/v2/", // packagePushDest.FullName,
                    _APIKey, // api key
                    null, // symbols source
                    null, // symbols api key
                    0, // timeout
                    false, // disable buffering
                    false, // no symbols,
                    false, // no skip duplicate
                    false, // enable server endpoint
                    log).GetAwaiter().GetResult();
 
        }
 
 
 
 
 
 
 
 
 
 
 
 
        private string publishNameParamSet()
        {
            var modulePath = "";
            // 1) run get-module to find the right package version
            // _name, _requiredVersion, _prerelease
 
 
            NuGetVersion nugetVersion;
 
            // parse version,
            // throw if version is in incorrect format
            if (!string.IsNullOrEmpty(_requiredVersion))
            {
                // check if exact version
 
                //VersionRange versionRange = VersionRange.Parse(version);
                NuGetVersion.TryParse(_requiredVersion, out nugetVersion);
 
                // if the version didn't parse correctly, throw error
                if (string.IsNullOrEmpty(nugetVersion.ToNormalizedString()))
                {
                    var message = $"-RequiredVersion is not in a proper package version. Please specify a version number, for example: '2.0.0'";
                    var ex = new ArgumentException(message); // System.ArgumentException vs PSArgumentException
 
                    var requiredVersionError = new ErrorRecord(ex, "RequiredVersionError", ErrorCategory.InvalidArgument, null);
 
                    this.ThrowTerminatingError(requiredVersionError);
                }
            }
 
            if (_prerelease)
            {
                // add prerelease flag
            }
 
            // return the full path
            return modulePath;
        }
 
 
        private string createNuspec(string outputDir, string moduleFile, string pkgName)
        {
            /////////////////// create a nuspec
            WriteVerbose("Creating new nuspec file.");
 
 
            // retrieving information from the .psd1 or .ps1
  
 
 
            if (moduleFile.EndsWith(".psd1"))
            {
 
                // the file, read it line by line, and split it by tabs. Then we grab the second integer and loop through the rest to find the path.
 
                using (StreamReader reader = File.OpenText(moduleFile))
                {
                    string line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        string[] items = line.Split('\t');
                        int myInteger = int.Parse(items[1]); // Here's your integer.
 
                        // Now let's find the path.
                        string path = null;
                        foreach (string item in items)
                        {
                            if (item.StartsWith("item\\") && item.EndsWith(".ddj"))
                                path = item;
                        }
 
                        // At this point, `myInteger` and `path` contain the values we want
                        // for the current line. We can then store those values or print them,
                        // or anything else we like.
                    }
                };
            }
            else if (moduleFile.EndsWith(".ps1"))
            {
                    // TODO COME BACK HERE!!!!!
 
 
             
            }
         
 
 
 
 
            var nameSpaceUri = "http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd";
            var doc = new XmlDocument();
 
            // xml declaration is recommended, but not mandatory
            XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "utf-8", null);
            XmlElement root = doc.DocumentElement;
            doc.InsertBefore(xmlDeclaration, root);
 
            // create top-level elements
            XmlElement packageElement = doc.CreateElement("package", "package", nameSpaceUri);
            XmlElement metadataElement = doc.CreateElement("metadata", "metadata", nameSpaceUri);
 
            //using (StreamWriter sw = new StreamWriter(fullinstallPath))
 
            SortedDictionary<string, string> metadataElementsDictionary = new SortedDictionary<string, string>();
 
            metadataElementsDictionary.Add("id", pkgName);
// metadataElementsDictionary.Add("version", pkgMetadata["ModuleVersion"].ToString());
// metadataElementsDictionary.Add("authors", pkgMetadata["Author"].ToString());
 
// metadataElementsDictionary.Add("owners", pkgMetadata["CompanyName"].ToString());
 
// metadataElementsDictionary.Add("requireLicenseAcceptance", _requireLicenseAcceptance.ToString().ToLower());
 
// metadataElementsDictionary.Add("description", pkgMetadata["Description"].ToString());
// metadataElementsDictionary.Add("copyright", pkgMetadata["Copyright"].ToString());
 
 
 
 
 
 
            metadataElementsDictionary.Add("releaseNotes", _releaseNotes);
 
 
 
            if (_tags.Any())
            {
                //s.Add("tags", _tags);
            }
 
            if (!string.IsNullOrEmpty(_licenseUrl))
            {
                metadataElementsDictionary.Add("licenseUrl", _licenseUrl);
            }
            else
            {
             
            }
 
            if (!string.IsNullOrEmpty(_projectUrl))
            {
                metadataElementsDictionary.Add("projectUrl", _projectUrl);
            }
            else
            {
                // read from .psd1?
            }
 
            if (!string.IsNullOrEmpty(_iconUrl))
            {
                metadataElementsDictionary.Add("iconUrl", _iconUrl);
            }
            else
            {
             
            }
 
 
 
            foreach (var key in metadataElementsDictionary.Keys)
            {
                XmlElement element = doc.CreateElement(key, key, nameSpaceUri);
 
                string elementInnerText;
                metadataElementsDictionary.TryGetValue(key, out elementInnerText);
                element.InnerText = elementInnerText;
 
                metadataElement.AppendChild(element);
            }
 
 
 
/* Hashtable[] dependencies = (Hashtable[])pkgMetadata["RequiredModules"];
 
            if (dependencies != null)
            {
                XmlElement dependenciesElement = doc.CreateElement("dependencies", "dependencies", nameSpaceUri);
 
                foreach (Hashtable dependency in dependencies)
                {
                    XmlElement element = doc.CreateElement("dependency", "dependency", nameSpaceUri);
 
                    element.SetAttribute("id", dependency["ModuleName"].ToString());
                    if (!string.IsNullOrEmpty(dependency["ModuleVersion"].ToString()))
                    {
                        element.SetAttribute("version", dependency["ModuleVersion"].ToString());
                    }
 
                    dependenciesElement.AppendChild(element);
                }
                metadataElement.AppendChild(dependenciesElement);
            }
*/
 
            XmlElement filesElement = null;
            /*
            if (_files)
            {
 
                filesElement = doc.CreateElement("files", "files", nameSpaceUri);
 
                foreach (var file in _files)
                {
                    XmlElement element = doc.CreateElement("file", "file", nameSpaceUri);
 
                    element.SetAttribute("src", file.src);
                    if (file.target) { element.SetAttribute("target", file.target); }
                    if (file.exclude) { element.SetAttribute("exclude", file.exclude); }
 
                    filesElement.AppendChild(element);
                }
            }
            */
            packageElement.AppendChild(metadataElement);
            if (filesElement != null) { packageElement.AppendChild(filesElement); }
 
            doc.AppendChild(packageElement);
 
 
 
            var nuspecFullName = System.IO.Path.Combine(outputDir, pkgName + ".nuspec");
            doc.Save(nuspecFullName);
 
            this.WriteVerbose("The newly created nuspec is: " + nuspecFullName);
 
            return nuspecFullName;
            ///////////////// done creating nuspec
        }
    }
}