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 } } } |