From ffa51f7e2fce411c1b539c358cdd814282489311 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 13 Jul 2022 21:32:34 -0400 Subject: [PATCH 01/29] change design to use classes for Metadata, Content, Requires,HelpInfo --- src/PowerShellGet.psd1 | 1 + src/code/NewPSScriptFileInfo2.cs | 258 +++++++++++ src/code/PSScriptContents.cs | 114 +++++ src/code/PSScriptFileInfo2.cs | 207 +++++++++ src/code/PSScriptHelp.cs | 289 +++++++++++++ src/code/PSScriptMetadata.cs | 400 ++++++++++++++++++ src/code/PSScriptRequires.cs | 77 ++++ src/code/Utils.cs | 131 +++++- ...CoreApp,Version=v6.0.AssemblyAttributes.cs | 4 + .../Debug/net6.0/benchmarks.AssemblyInfo.cs | 23 + .../benchmarks.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 10 + .../benchmarks.csproj.AssemblyReference.cache | Bin 0 -> 1039 bytes ...CoreApp,Version=v6.0.AssemblyAttributes.cs | 4 + .../Release/net6.0/benchmarks.AssemblyInfo.cs | 23 + .../benchmarks.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 10 + 17 files changed, 1549 insertions(+), 4 deletions(-) create mode 100644 src/code/NewPSScriptFileInfo2.cs create mode 100644 src/code/PSScriptContents.cs create mode 100644 src/code/PSScriptFileInfo2.cs create mode 100644 src/code/PSScriptHelp.cs create mode 100644 src/code/PSScriptMetadata.cs create mode 100644 src/code/PSScriptRequires.cs create mode 100644 test/perf/benchmarks/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs create mode 100644 test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfo.cs create mode 100644 test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfoInputs.cache create mode 100644 test/perf/benchmarks/obj/Debug/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 test/perf/benchmarks/obj/Debug/net6.0/benchmarks.csproj.AssemblyReference.cache create mode 100644 test/perf/benchmarks/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs create mode 100644 test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfo.cs create mode 100644 test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfoInputs.cache create mode 100644 test/perf/benchmarks/obj/Release/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index 2c9b767b2..d35878d98 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -21,6 +21,7 @@ 'Register-PSResourceRepository', 'Save-PSResource', 'Set-PSResourceRepository', + 'New-PSScriptFileInfo2' 'Publish-PSResource', 'Uninstall-PSResource', 'Unregister-PSResourceRepository', diff --git a/src/code/NewPSScriptFileInfo2.cs b/src/code/NewPSScriptFileInfo2.cs new file mode 100644 index 000000000..7bfa3a5dd --- /dev/null +++ b/src/code/NewPSScriptFileInfo2.cs @@ -0,0 +1,258 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.IO; +using System.Management.Automation; +using Microsoft.PowerShell.Commands; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// Creates a new .ps1 file with script information required for publishing a script. + /// + [Cmdlet(VerbsCommon.New, "PSScriptFileInfo2")] + public sealed class NewPSScriptFileInfo2 : PSCmdlet + { + #region Parameters + + /// + /// The path the .ps1 script info file will be created at. + /// + [Parameter(Position = 0, Mandatory = true)] + [ValidateNotNullOrEmpty] + public string FilePath { get; set; } + + /// + /// The version of the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Version { get; set; } + + /// + /// The author of the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Author { get; set; } + + /// + /// The description of the script. + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string Description { get; set; } + + /// + /// A unique identifier for the script. The GUID can be used to distinguish among scripts with the same name. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public Guid Guid { get; set; } + + /// + /// The name of the company owning the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string CompanyName { get; set; } + + /// + /// The copyright statement for the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Copyright { get; set; } + + /// + /// The list of modules required by the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public Hashtable[] RequiredModules { get; set; } + + /// + /// The list of external module dependencies taken by this script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] ExternalModuleDependencies { get; set; } + + /// + /// The list of scripts required by the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] RequiredScripts { get; set; } + + /// + /// The list of external script dependencies taken by this script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] ExternalScriptDependencies { get; set; } + + /// + /// The tags associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] Tags { get; set; } + + /// + /// The Uri for the project associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string ProjectUri { get; set; } + + /// + /// The Uri for the license associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string LicenseUri { get; set; } + + /// + /// The Uri for the icon associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string IconUri { get; set; } + + /// + /// The release notes for the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string ReleaseNotes { get; set; } + + /// + /// The private data associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string PrivateData { get; set; } + + /// + /// If used with Path parameter and .ps1 file specified at the path exists, it rewrites the file. + /// + [Parameter] + public SwitchParameter Force { get; set; } + + #endregion + + #region Methods + + protected override void EndProcessing() + { + // validate Uri related parameters passed in as strings + Uri projectUri = null; + if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri, + cmdletPassedIn: this, + uriResult: out projectUri, + errorRecord: out ErrorRecord projectErrorRecord)) + { + ThrowTerminatingError(projectErrorRecord); + } + + Uri licenseUri = null; + if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUri(uriString: LicenseUri, + cmdletPassedIn: this, + uriResult: out licenseUri, + errorRecord: out ErrorRecord licenseErrorRecord)) + { + ThrowTerminatingError(licenseErrorRecord); + } + + Uri iconUri = null; + if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUri(uriString: IconUri, + cmdletPassedIn: this, + uriResult: out iconUri, + errorRecord: out ErrorRecord iconErrorRecord)) + { + ThrowTerminatingError(iconErrorRecord); + } + + if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + { + var exMessage = "Path needs to end with a .ps1 file. Example: C:/Users/john/x/MyScript.ps1"; + var ex = new ArgumentException(exMessage); + var InvalidPathError = new ErrorRecord(ex, "InvalidPath", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InvalidPathError); + } + + var resolvedFilePath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(FilePath); + if (String.IsNullOrEmpty(resolvedFilePath)) + { + var exMessage = "Error: Could not resolve provided Path argument into a single path."; + var ex = new PSArgumentException(exMessage); + var InvalidPathArgumentError = new ErrorRecord(ex, "InvalidPathArgumentError", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InvalidPathArgumentError); + } + + if (File.Exists(resolvedFilePath) && !Force) + { + // .ps1 file at specified location already exists and Force parameter isn't used to rewrite the file + var exMessage = ".ps1 file at specified path already exists. Specify a different location or use -Force parameter to overwrite the .ps1 file."; + var ex = new ArgumentException(exMessage); + var ScriptAtPathAlreadyExistsError = new ErrorRecord(ex, "ScriptAtPathAlreadyExists", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(ScriptAtPathAlreadyExistsError); + } + + ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; + if (RequiredModules != null && RequiredModules.Length > 0) + { + if (!Utils.TryCreateModuleSpecification( + moduleSpecHashtables: RequiredModules, + out validatedRequiredModuleSpecifications, + out ErrorRecord[] moduleSpecErrors)) + { + foreach (ErrorRecord err in moduleSpecErrors) + { + WriteError(err); + } + + return; + } + } + + PSScriptFileInfo2 scriptInfo = new PSScriptFileInfo2( + version: Version, + guid: Guid, + author: Author, + companyName: CompanyName, + copyright: Copyright, + tags: Tags, + licenseUri: licenseUri, + projectUri: projectUri, + iconUri: iconUri, + requiredModules: validatedRequiredModuleSpecifications, + externalModuleDependencies: ExternalModuleDependencies, + requiredScripts: RequiredScripts, + externalScriptDependencies: ExternalScriptDependencies, + releaseNotes: ReleaseNotes, + privateData: PrivateData, + description: Description); + + if (!scriptInfo.TryCreateScriptFileInfoString( + psScriptFileString: out string psScriptFileContents, + errors: out ErrorRecord[] errors)) + { + foreach (ErrorRecord err in errors) + { + WriteError(err); + } + + return; + } + + File.WriteAllText(resolvedFilePath, psScriptFileContents); + } + + #endregion + } +} diff --git a/src/code/PSScriptContents.cs b/src/code/PSScriptContents.cs new file mode 100644 index 000000000..e0b6cf0d7 --- /dev/null +++ b/src/code/PSScriptContents.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Linq; +using System.Collections.ObjectModel; +using Microsoft.PowerShell.Commands; +using NuGet.Versioning; + +namespace Microsoft.PowerShell.PowerShellGet.UtilClasses +{ + /// + /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents). + /// + public sealed class PSScriptContents + { + #region Properties + + /// + /// End of file contents for the .ps1 file. + /// + public string EndOfFileContents { get; private set; } = String.Empty; + + /// + /// End of file contents for the .ps1 file. + /// + public bool ContainsSignature { get; set; } + + #endregion + + #region Private Members + + private const string signatureStartString = "# SIG # Begin signature block"; + + #endregion + + #region Constructor + + public PSScriptContents(string endOfFileContents) + { + this.EndOfFileContents = endOfFileContents; + this.ContainsSignature = CheckForSignature(); + } + + #endregion + + #region Public Methods + + public void ParseContent(string[] commentLines, out ErrorRecord[] errors, bool removeSignature) + { + errors = null; + EndOfFileContents = String.Join("", commentLines); + ContainsSignature = CheckForSignature(); + } + + /// + /// Checks if end of file contents contains a Signature + /// + public bool ValidateContent() + { + // if (ContainsSignature) + // { + // // todo: write warning somewhere, change state of ContainsSignature or should it reflect original file state? + // // RemoveSignatureString(); + + // } + + // return true; + return !ContainsSignature; + } + + public string EmitContent() + { + return EndOfFileContents; + } + + + + #endregion + + #region Private Methods + + /// + /// Checks if the end of file contents contain a signature + /// + private bool CheckForSignature() + { + return (EndOfFileContents.Contains(signatureStartString)); + } + + /// + /// Removes the signature from EndOfFileContents property + /// as the signature would be invalidated during update. + /// + private void RemoveSignatureString() + { + if (ContainsSignature) + { + int signatureStartIndex = EndOfFileContents.IndexOf(signatureStartString); + EndOfFileContents = EndOfFileContents.Substring(0, signatureStartIndex); + } + + // TODO: should I set ContainsSignature to false now? + } + #endregion + } +} \ No newline at end of file diff --git a/src/code/PSScriptFileInfo2.cs b/src/code/PSScriptFileInfo2.cs new file mode 100644 index 000000000..e72d31b5d --- /dev/null +++ b/src/code/PSScriptFileInfo2.cs @@ -0,0 +1,207 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Linq; +using System.Collections.ObjectModel; +using Microsoft.PowerShell.Commands; +using NuGet.Versioning; + +namespace Microsoft.PowerShell.PowerShellGet.UtilClasses +{ + /// + /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents). + /// + public sealed class PSScriptFileInfo2 + { + #region Properties + public PSScriptMetadata ScriptMetadataCommment { get; set; } + + public PSScriptHelp ScriptHelpComment { get; set; } + + public PSScriptRequires ScriptRequiresComment { get; set; } + + public PSScriptContents ScriptContent { get; set; } + + #endregion + + #region Constructor + + public PSScriptFileInfo2( + string version, + Guid guid, + string author, + string companyName, + string copyright, + string[] tags, + Uri licenseUri, + Uri projectUri, + Uri iconUri, + ModuleSpecification[] requiredModules, + string[] externalModuleDependencies, + string[] requiredScripts, + string[] externalScriptDependencies, + string releaseNotes, + string privateData, + string description) + { + PSScriptMetadata scriptMetadataComment = new PSScriptMetadata( + version, + guid, + author, + companyName, + copyright, + tags, + licenseUri, + projectUri, + iconUri, + // requiredModules, + externalModuleDependencies, + requiredScripts, + externalScriptDependencies, + releaseNotes, + privateData); + + PSScriptHelp scriptHelpComment = new PSScriptHelp(description); + PSScriptRequires scriptRequiresComment = new PSScriptRequires(requiredModules); + PSScriptContents scriptRemainingContent = new PSScriptContents(String.Empty); + + this.ScriptMetadataCommment = scriptMetadataComment; + this.ScriptHelpComment = scriptHelpComment; + this.ScriptRequiresComment = scriptRequiresComment; + this.ScriptContent = scriptRemainingContent; + } + + public PSScriptFileInfo2( + PSScriptMetadata scriptMetadataComment, + PSScriptHelp scriptHelpComment, + PSScriptRequires scriptRequiresComment, + PSScriptContents scriptRemainingContent + ) + { + this.ScriptMetadataCommment = scriptMetadataComment; + this.ScriptHelpComment = scriptHelpComment; + this.ScriptRequiresComment = scriptRequiresComment; + this.ScriptContent = scriptRemainingContent; + } + + #endregion + + #region Internal Static Methods + + internal static bool TryParseScriptFile( + string scriptFileInfoPath, + out PSScriptFileInfo2 parsedScript, + out ErrorRecord[] errors, + out string[] msgs + ) + { + // parse -> create object -> validate + parsedScript = null; + errors = null; + msgs = new string[]{}; + return true; + } + + internal bool TryCreateScriptFileInfoString( + out string psScriptFileString, + out ErrorRecord[] errors + ) + { + psScriptFileString = String.Empty; + errors = new ErrorRecord[]{}; + List errorsList = new List(); + + bool fileContentsSuccessfullyCreated = true; + + + // step 1: validate + if (!ScriptMetadataCommment.ValidateContent(out ErrorRecord[] metadataValidationErrors)) + { + errorsList.AddRange(metadataValidationErrors); + fileContentsSuccessfullyCreated = false; + + } + + if (!ScriptHelpComment.ValidateContent(out ErrorRecord helpValidationError)) + { + errorsList.Add(helpValidationError); + fileContentsSuccessfullyCreated = false; + } + + // if (!ScriptRequiresComment.ValidateContent()) + // { + // fileContentsSuccessfullyCreated = false; + // // store error and return + // // I think the method had a way of checking its not empty if it attemps to write + // } + + // if (!ScriptContent.ValidateContent()) + // { + + // } + + if (!fileContentsSuccessfullyCreated) + { + errors = errorsList.ToArray(); + return fileContentsSuccessfullyCreated; + } + + // todo: validate endofffilecontents here? + + // step: try to write + psScriptFileString = ScriptMetadataCommment.EmitContent(); + + string psRequiresCommentBlock = ScriptRequiresComment.EmitContent(); + if (!String.IsNullOrEmpty(psRequiresCommentBlock)) + { + psScriptFileString += "\n"; + psScriptFileString += psRequiresCommentBlock; + } + + psScriptFileString += "\n"; // need a newline after last #> and before <# for script comment block, TODO: try removing + // or else not recongnized as a valid comment help info block when parsing the created ps1 later + psScriptFileString += "\n" + ScriptHelpComment.EmitContent(); + + string psEndOfFileContent = ScriptContent.EmitContent(); + if (!String.IsNullOrEmpty(psEndOfFileContent)) + { + // todo: remove signature here? + psScriptFileString += "\n" + psEndOfFileContent; + } + + + return fileContentsSuccessfullyCreated; + + } + + + internal static bool TryUpdateScriptFileContents( + PSScriptFileInfo2 scriptInfo, + out string updatedPSScriptFileContents, + out ErrorRecord[] errors + ) + { + updatedPSScriptFileContents = string.Empty; + errors = null; + return true; + } + + + + + + + + #endregion + + + } +} diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs new file mode 100644 index 000000000..6829769ba --- /dev/null +++ b/src/code/PSScriptHelp.cs @@ -0,0 +1,289 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Linq; +using System.Collections.ObjectModel; +using Microsoft.PowerShell.Commands; +using NuGet.Versioning; + +namespace Microsoft.PowerShell.PowerShellGet.UtilClasses +{ + /// + /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents). + /// + public sealed class PSScriptHelp + { + #region Properties + + /// + /// The description of the script. + /// + public string Description { get; private set; } + + /// + /// The synopsis of the script. + /// + public string Synopsis { get; private set; } + + /// + /// The example(s) relating to the script's usage. + /// + public string[] Example { get; private set; } = new string[]{}; + + /// + /// The inputs to the script. + /// + public string[] Inputs { get; private set; } = new string[]{}; + + /// + /// The outputs to the script. + /// + public string[] Outputs { get; private set; } = new string[]{}; + + /// + /// The notes for the script. + /// + public string[] Notes { get; private set; } = new string[]{}; + + /// + /// The links for the script. + /// + public string[] Links { get; private set; } = new string[]{}; + + /// + /// The components for the script. + /// + public string[] Component { get; private set; } = new string[]{}; + + /// + /// The roles for the script. + /// + public string[] Role { get; private set; } = new string[]{}; + + /// + /// The functionality components for the script. + /// + public string[] Functionality { get; private set; } = new string[]{}; + + #endregion + + #region Constructor + + public PSScriptHelp (string description) + { + this.Description = description; + } + + public PSScriptHelp ( + string description, + string synopsis, + string[] example, + string[] inputs, + string[] outputs, + string[] notes, + string[] links, + string[] component, + string[] role, + string[] functionality) + { + this.Description = description; + this.Synopsis = synopsis; + this.Example = example; + this.Inputs = inputs; + this.Outputs = outputs; + this.Notes = notes; + this.Links = links; + this.Component = component; + this.Role = role; + this.Functionality = functionality; + } + + #endregion + + #region Internal Methods + + internal bool ParseContentIntoObj(string[] commentLines) + { + bool successfullyParsed = false; + char[] spaceDelimeter = new char[]{' '}; + char[] newlineDelimeter = new char[]{'\n'}; + + // parse content into a hashtable + Hashtable parsedHelpMetadata = ParseContent(commentLines); + + // populate object + Description = (string) parsedHelpMetadata["DESCRIPTION"] ?? String.Empty; + + Synopsis = (string) parsedHelpMetadata["SYNOPSIS"] ?? String.Empty; + Example = Utils.GetStringArrayFromString(newlineDelimeter, (string) parsedHelpMetadata["EXAMPLE"]); + Inputs = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["INPUT"]); + Outputs = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["OUTPUTS"]); + Notes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["NOTES"]); + Links = Utils.GetStringArrayFromString(newlineDelimeter, (string) parsedHelpMetadata["LINKS"]); + Component = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["COMPONENT"]); + Role = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["ROLE"]); + Functionality = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["FUNCTIONALITY"]); + + // TODO: validate obj here? + + + return successfullyParsed; + } + + internal Hashtable ParseContent(string[] commentLines) + { + Hashtable parsedHelpMetadata = new Hashtable(); + string keyName = ""; + string value = ""; + + for (int i = 1; i < commentLines.Count(); i++) + { + string line = commentLines[i]; + + // scenario where line is: .KEY VALUE + // this line contains a new metadata property. + if (line.Trim().StartsWith(".")) + { + // check if keyName was previously populated, if so add this key value pair to the metadata hashtable + if (!String.IsNullOrEmpty(keyName)) + { + parsedHelpMetadata.Add(keyName, value); + } + + string[] parts = line.Trim().TrimStart('.').Split(); + keyName = parts[0]; + value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; + } + else if (!String.IsNullOrEmpty(line)) + { + // scenario where line contains text that is a continuation of value from previously recorded key + // this line does not starting with .KEY, and is also not an empty line. + if (value.Equals(String.Empty)) + { + value += line; + } + else + { + value += Environment.NewLine + line; + } + } + } + + // this is the case where last key value had multi-line value. + // and we've captured it, but still need to add it to hashtable. + if (!String.IsNullOrEmpty(keyName) && !parsedHelpMetadata.ContainsKey(keyName)) + { + // only add this key value if it hasn't already been added + parsedHelpMetadata.Add(keyName, value); + } + + return parsedHelpMetadata; + } + + internal bool ValidateContent(out ErrorRecord error) + { + error = null; + if (String.IsNullOrEmpty(Description)) + { + var exMessage = "PSScript file must contain value for Description. Ensure value for Description is passed in and try again."; + var ex = new ArgumentException(exMessage); + var PSScriptInfoMissingDescriptionError = new ErrorRecord(ex, "PSScriptInfoMissingDescription", ErrorCategory.InvalidArgument, null); + error = PSScriptInfoMissingDescriptionError; + return false; + } + + if (StringContainsComment(Description)) + { + var exMessage = "PSScript file's value for Description cannot contain '<#' or '#>'. Pass in a valid value for Description and try again."; + var ex = new ArgumentException(exMessage); + var DescriptionContainsCommentError = new ErrorRecord(ex, "DescriptionContainsComment", ErrorCategory.InvalidArgument, null); + error = DescriptionContainsCommentError; + return false; + } + + return true; + } + + internal string EmitContent() + { + string psHelpInfo; + List psHelpInfoLines = new List(); + + psHelpInfoLines.Add("<#\n"); + psHelpInfoLines.Add(String.Format(".DESCRIPTION\n{0}", Description)); + + if (!String.IsNullOrEmpty(Synopsis)) + { + psHelpInfoLines.Add(String.Format(".SYNOPSIS\n{0}", Synopsis)); + } + + foreach (string currentExample in Example) + { + psHelpInfoLines.Add(String.Format(".EXAMPLE\n{0}", currentExample)); + } + + foreach (string input in Inputs) + { + psHelpInfoLines.Add(String.Format(".INPUTS\n{0}", input)); + } + + foreach (string output in Outputs) + { + psHelpInfoLines.Add(String.Format(".OUTPUTS\n{0}", output)); + } + + if (Notes.Length > 0) + { + psHelpInfoLines.Add(String.Format(".NOTES\n{0}", String.Join("\n", Notes))); + } + + foreach (string link in Links) + { + psHelpInfoLines.Add(String.Format(".LINK\n{0}", link)); + } + + if (Component.Length > 0) + { + psHelpInfoLines.Add(String.Format(".COMPONENT\n{0}", String.Join("\n", Component))); + } + + if (Role.Length > 0) + { + psHelpInfoLines.Add(String.Format(".ROLE\n{0}", String.Join("\n", Role))); + } + + if (Functionality.Length > 0) + { + psHelpInfoLines.Add(String.Format(".FUNCTIONALITY\n{0}", String.Join("\n", Functionality))); + } + + psHelpInfo = String.Join("\n", psHelpInfoLines); + psHelpInfo = psHelpInfo.TrimEnd('\n'); + psHelpInfo += "\n\n#>"; + + return psHelpInfo; + } + + #endregion + + #region Private Methods + + /// + /// Ensure description field (passed as stringToValidate) does not contain '<#' or '#>' + /// + private bool StringContainsComment(string stringToValidate) + { + return stringToValidate.Contains("<#") || stringToValidate.Contains("#>"); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs new file mode 100644 index 000000000..cb24b71ce --- /dev/null +++ b/src/code/PSScriptMetadata.cs @@ -0,0 +1,400 @@ +using System.Net.Http.Headers; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Linq; +using System.Collections.ObjectModel; +using Microsoft.PowerShell.Commands; +using NuGet.Versioning; + +namespace Microsoft.PowerShell.PowerShellGet.UtilClasses +{ + /// + /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents). + /// + public sealed class PSScriptMetadata + { + #region Properties + + /// + /// the version of the script. + /// + public NuGetVersion Version { get; private set; } + + /// + /// the GUID for the script. + /// + public Guid Guid { get; private set; } + + /// + /// the author for the script. + /// + public string Author { get; private set; } + + /// + /// the name of the company owning the script. + /// + public string CompanyName { get; private set; } + + /// + /// the copyright statement for the script. + /// + public string Copyright { get; private set; } + + /// + /// the tags for the script. + /// + public string[] Tags { get; private set; } + + /// + /// the Uri for the license of the script. + /// + public Uri LicenseUri { get; private set; } + + /// + /// the Uri for the project relating to the script. + /// + public Uri ProjectUri { get; private set; } + + /// + /// the Uri for the icon relating to the script. + /// + public Uri IconUri { get; private set; } + + /// + /// the list of external module dependencies for the script. + /// + public string[] ExternalModuleDependencies { get; private set; } = new string[]{}; + + /// + /// the list of required scripts for the parent script. + /// + public string[] RequiredScripts { get; private set; } = new string[]{}; + + /// + /// the list of external script dependencies for the script. + /// + public string[] ExternalScriptDependencies { get; private set; } = new string[]{}; + + /// + /// the release notes relating to the script. + /// + public string ReleaseNotes { get; private set; } = String.Empty; + + /// + /// The private data associated with the script. + /// + public string PrivateData { get; private set; } + + #endregion + + #region Constructor + + public PSScriptMetadata( + string version, + Guid guid, + string author, + string companyName, + string copyright, + string[] tags, + Uri licenseUri, + Uri projectUri, + Uri iconUri, + string[] externalModuleDependencies, + string[] requiredScripts, + string[] externalScriptDependencies, + string releaseNotes, + string privateData) + { + if (String.IsNullOrEmpty(author)) + { + author = Environment.UserName; + } + + Version = !String.IsNullOrEmpty(version) ? new NuGetVersion (version) : new NuGetVersion("1.0.0.0"); + Guid = (guid == null || guid == Guid.Empty) ? Guid.NewGuid() : guid; + Author = !String.IsNullOrEmpty(author) ? author : Environment.UserName; + CompanyName = companyName; + Copyright = copyright; + Tags = tags ?? Utils.EmptyStrArray; + LicenseUri = licenseUri; + ProjectUri = projectUri; + IconUri = iconUri; + ExternalModuleDependencies = externalModuleDependencies ?? Utils.EmptyStrArray; + RequiredScripts = requiredScripts ?? Utils.EmptyStrArray; + ExternalScriptDependencies = externalScriptDependencies ?? Utils.EmptyStrArray; + ReleaseNotes = releaseNotes; + PrivateData = privateData; + } + + internal PSScriptMetadata() {} + + #endregion + + #region Internal Methods + + /// + /// Parses script metadata comment (passed in as its lines) into PSScriptMetadata instance's properties + /// Also validates that this metadata has required script properties + /// + internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] errors, out string[] msgs) + { + msgs = new string[]{}; + List msgsList = new List(); + errors = new ErrorRecord[]{}; + + // parse content into a hashtable + Hashtable parsedMetadata = ParseContent(commentLines); + + // // and then validate contents on the parsed content (for required Author, Version, Guid metadata) + // if (!ValidateContent(out errors)) + // { + // return false; + // } + + char[] spaceDelimeter = new char[]{' '}; + + Uri parsedLicenseUri = null; + if (!String.IsNullOrEmpty((string) parsedMetadata["LICENSEURI"])) + { + if (!Uri.TryCreate((string) parsedMetadata["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri)) + { + msgsList.Add($"LicenseUri property {(string) parsedMetadata["LICENSEURI"]} could not be created as a Uri"); + } + } + + Uri parsedProjectUri = null; + if (!String.IsNullOrEmpty((string) parsedMetadata["PROJECTURI"])) + { + if (!Uri.TryCreate((string) parsedMetadata["PROJECTURI"], UriKind.Absolute, out parsedProjectUri)) + { + msgsList.Add($"ProjectUri property {(string) parsedMetadata["PROJECTURI"]} could not be created as Uri"); + } + } + + Uri parsedIconUri = null; + if (!String.IsNullOrEmpty((string) parsedMetadata["ICONURI"])) + { + if (!Uri.TryCreate((string) parsedMetadata["ICONURI"], UriKind.Absolute, out parsedIconUri)) + { + msgsList.Add($"IconUri property {(string) parsedMetadata["ICONURI"]} could not be created as Uri"); + } + } + + // now populate PSScriptMetadata object properties with parsed metadata + Author = (string) parsedMetadata["AUTHOR"]; + Version = new NuGetVersion((string) parsedMetadata["VERSION"]); + Guid = new Guid((string) parsedMetadata["GUID"]); + + CompanyName = (string) parsedMetadata["COMPANYNAME"] ?? String.Empty; + Copyright = (string) parsedMetadata["COPYRIGHT"] ?? String.Empty; + + LicenseUri = parsedLicenseUri; + ProjectUri = parsedProjectUri; + IconUri = parsedIconUri; + + Tags = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedMetadata["TAGS"]);; + ExternalModuleDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedMetadata["EXTERNALMODULEDEPENDENCIES"]); + RequiredScripts = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedMetadata["REQUIREDSCRIPTS"]); + ExternalScriptDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedMetadata["EXTERNALSCRIPTDEPENDENCIES"]); + ReleaseNotes = (string) parsedMetadata["RELEASENOTES"] ?? String.Empty; + PrivateData = (string) parsedMetadata["PRIVATEDATA"] ?? String.Empty; + + + msgs = msgsList.ToArray(); + return true; + } + + /// + /// Parses metadata out of of comment block's lines (which are passed in) into a hashtable + /// + internal Hashtable ParseContent(string[] commentLines) + { + /** + comment lines will look like this: + + .VERSION 1.0 + + .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd + + .AUTHOR + + .COMPANYNAME + + .COPYRIGHT + + .TAGS + + .LICENSEURI + + .PROJECTURI + + .ICONURI + + .EXTERNALMODULEDEPENDENCIES + + .REQUIREDSCRIPTS + + .EXTERNALSCRIPTDEPENDENCIES + + .RELEASENOTES + some notes + + .PRIVATEDATA + some data + + */ + + Hashtable parsedMetadata = new Hashtable(); + + string keyName = ""; + string value = ""; + + for (int i = 1; i < commentLines.Count(); i++) + { + string line = commentLines[i]; + + // scenario where line is: .KEY VALUE + // this line contains a new metadata property. + if (line.Trim().StartsWith(".")) + { + // check if keyName was previously populated, if so add this key value pair to the metadata hashtable + if (!String.IsNullOrEmpty(keyName)) + { + parsedMetadata.Add(keyName, value); + } + + string[] parts = line.Trim().TrimStart('.').Split(); + keyName = parts[0]; + value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; + } + else if (!String.IsNullOrEmpty(line)) + { + // scenario where line contains text that is a continuation of value from previously recorded key + // this line does not starting with .KEY, and is also not an empty line. + if (value.Equals(String.Empty)) + { + value += line; + } + else + { + value += Environment.NewLine + line; + } + } + } + + // this is the case where last key value had multi-line value. + // and we've captured it, but still need to add it to hashtable. + if (!String.IsNullOrEmpty(keyName) && !parsedMetadata.ContainsKey(keyName)) + { + // only add this key value if it hasn't already been added + parsedMetadata.Add(keyName, value); + } + + return parsedMetadata; + } + + /// + /// Validates metadata content parsed from .ps1 is valid and contains required script properties + /// i.e Author, Version, Guid + /// + internal bool ValidateContent(out ErrorRecord[] errors) + { + bool validPSScriptInfo = true; + List errorsList = new List(); + + if (Version == null || String.IsNullOrEmpty(Version.ToString())) + { + var message = String.Format("PSScript file is missing the required Version property"); + var ex = new ArgumentException(message); + var psScriptMissingVersionError = new ErrorRecord(ex, "psScriptMissingVersion", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingVersionError); + validPSScriptInfo = false; + } + + if (String.IsNullOrEmpty(Author)) + { + var message = String.Format("PSScript file is missing the required Author property"); + var ex = new ArgumentException(message); + var psScriptMissingAuthorError = new ErrorRecord(ex, "psScriptMissingAuthor", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingAuthorError); + validPSScriptInfo = false; + } + + if (Guid == Guid.Empty) + { + var message = String.Format("PSScript file is missing the required Guid property"); + var ex = new ArgumentException(message); + var psScriptMissingGuidError = new ErrorRecord(ex, "psScriptMissingGuid", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingGuidError); + validPSScriptInfo = false; + } + + errors = errorsList.ToArray(); + return validPSScriptInfo; + } + + /// + /// Emits string representation of '<#PSScriptInfo ... #>' comment and its metadata contents + /// + internal string EmitContent() + { + /** + PSScriptInfo comment will be in following format: + <#PSScriptInfo + .VERSION 1.0 + .GUID 544238e3-1751-4065-9227-be105ff11636 + .AUTHOR manikb + .COMPANYNAME Microsoft Corporation + .COPYRIGHT (c) 2015 Microsoft Corporation. All rights reserved. + .TAGS Tag1 Tag2 Tag3 + .LICENSEURI https://contoso.com/License + .PROJECTURI https://contoso.com/ + .ICONURI https://contoso.com/Icon + .EXTERNALMODULEDEPENDENCIES ExternalModule1 + .REQUIREDSCRIPTS Start-WFContosoServer,Stop-ContosoServerScript + .EXTERNALSCRIPTDEPENDENCIES Stop-ContosoServerScript + .RELEASENOTES + contoso script now supports following features + Feature 1 + Feature 2 + Feature 3 + Feature 4 + Feature 5 + .PRIVATEDATA + #> + */ + + List psScriptInfoLines = new List(); + + psScriptInfoLines.Add("<#PSScriptInfo"); + psScriptInfoLines.Add(String.Format(".VERSION {0}", Version.ToString())); + psScriptInfoLines.Add(String.Format(".GUID {0}", Guid.ToString())); + psScriptInfoLines.Add(String.Format(".AUTHOR {0}", Author)); + psScriptInfoLines.Add(String.Format(".COMPANYNAME {0}", CompanyName)); + psScriptInfoLines.Add(String.Format(".COPYRIGHT {0}", Copyright)); + psScriptInfoLines.Add(String.Format(".TAGS {0}", String.Join(" ", Tags))); + psScriptInfoLines.Add(String.Format(".LICENSEURI {0}", LicenseUri == null ? String.Empty : LicenseUri.ToString())); + psScriptInfoLines.Add(String.Format(".PROJECTURI {0}", ProjectUri == null ? String.Empty : ProjectUri.ToString())); + psScriptInfoLines.Add(String.Format(".ICONURI {0}", IconUri == null ? String.Empty : IconUri.ToString())); + psScriptInfoLines.Add(String.Format(".EXTERNALMODULEDEPENDENCIES {0}", String.Join(" ", ExternalModuleDependencies))); + psScriptInfoLines.Add(String.Format(".REQUIREDSCRIPTS {0}", String.Join(" ", RequiredScripts))); + psScriptInfoLines.Add(String.Format(".EXTERNALSCRIPTDEPENDENCIES {0}", String.Join(" ", ExternalScriptDependencies))); + psScriptInfoLines.Add(String.Format(".RELEASENOTES\n{0}", ReleaseNotes)); + psScriptInfoLines.Add(String.Format(".PRIVATEDATA\n{0}", PrivateData)); + psScriptInfoLines.Add("#>"); + + return String.Join("\n\n", psScriptInfoLines); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs new file mode 100644 index 000000000..36bc22821 --- /dev/null +++ b/src/code/PSScriptRequires.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Linq; +using System.Collections.ObjectModel; +using Microsoft.PowerShell.Commands; +using NuGet.Versioning; + +namespace Microsoft.PowerShell.PowerShellGet.UtilClasses +{ + /// + /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents). + /// + public sealed class PSScriptRequires + { + #region Properties + + /// + /// The list of modules required by the script. + /// Hashtable keys: GUID, MaxVersion, ModuleName (Required), RequiredVersion, Version. + /// + public ModuleSpecification[] RequiredModules { get; private set; } = new ModuleSpecification[]{}; + + #endregion + + #region Constructor + + public PSScriptRequires(ModuleSpecification[] requiredModules) + { + this.RequiredModules = requiredModules ?? new ModuleSpecification[]{}; + } + + #endregion + + #region Internal Methods + + internal bool ParseContent() + { + // use AST parser + return false; + } + + internal bool ValidateContent() + { + // use AST parser + return false; + } + + internal string EmitContent() + { + if (RequiredModules.Length > 0) + { + List psRequiresLines = new List(); + psRequiresLines.Add("\n"); + foreach (ModuleSpecification moduleSpec in RequiredModules) + { + psRequiresLines.Add(String.Format("#Requires -Module {0}", moduleSpec.ToString())); + } + + psRequiresLines.Add("\n"); + return String.Join("\n", psRequiresLines); + } + + return String.Empty; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/code/Utils.cs b/src/code/Utils.cs index ae72d6b21..3a9b5f4b6 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -16,6 +16,7 @@ using System.Runtime.InteropServices; using System.Security; using System.Security.Cryptography.X509Certificates; +using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -97,6 +98,17 @@ public static string QuoteName(string name) return "'" + CodeGeneration.EscapeSingleQuotedStringContent(name) + "'"; } + public static string[] GetStringArrayFromString(char[] delimeter, string stringToConvertToArray) + { + // this will be a string where entries are separated by space + if (String.IsNullOrEmpty(stringToConvertToArray)) + { + return Utils.EmptyStrArray; + } + + return stringToConvertToArray.Split(delimeter, StringSplitOptions.RemoveEmptyEntries); + } + /// /// Converts an ArrayList of object types to a string array. /// @@ -819,7 +831,7 @@ private static bool TryReadPSDataFile( } public static void ValidateModuleManifest(string moduleManifestPath, out string[] errorMsgs) - { + { List errorMsgsList = new List(); using (System.Management.Automation.PowerShell pwsh = System.Management.Automation.PowerShell.Create()) { @@ -866,11 +878,11 @@ public static void ValidateModuleManifest(string moduleManifestPath, out string[ { // This will handle version errors message = $"{pwsh.Streams.Error[0].ToString()} Run 'Test-ModuleManifest' to validate the module manifest."; - } - + } + errorMsgsList.Add(message); } - } + } errorMsgs = errorMsgsList.ToArray(); } @@ -914,6 +926,117 @@ public static Hashtable ConvertJsonToHashtable( return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; } + public static bool TryCreateModuleSpecification( + Hashtable[] moduleSpecHashtables, + out ModuleSpecification[] validatedModuleSpecs, + out ErrorRecord[] errors) + { + bool moduleSpecCreatedSuccessfully = true; + List errorList = new List(); + validatedModuleSpecs = new ModuleSpecification[]{}; + List moduleSpecsList = new List(); + + foreach(Hashtable moduleSpec in moduleSpecHashtables) + { + // ModuleSpecification(string) constructor for creating a ModuleSpecification when only ModuleName is provided + if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) + { + var exMessage = $"RequiredModules Hashtable entry {moduleSpec.ToString()} is missing a key 'ModuleName' and associated value, which is required for each module specification entry"; + var ex = new ArgumentException(exMessage); + var NameMissingModuleSpecError = new ErrorRecord(ex, "NameMissingInModuleSpecification", ErrorCategory.InvalidArgument, null); + errorList.Add(NameMissingModuleSpecError); + moduleSpecCreatedSuccessfully = false; + continue; + } + + // at this point it must contain ModuleName key. + string moduleSpecName = (string) moduleSpec["ModuleName"]; + ModuleSpecification currentModuleSpec = null; + if (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion")) + { + // pass to ModuleSpecification(string) constructor + // this constructor method would only throw for a null/empty string, which we've already validated against above + currentModuleSpec = new ModuleSpecification(moduleSpecName); + + if (currentModuleSpec != null) + { + moduleSpecsList.Add(currentModuleSpec); + } + else + { + var exMessage = $"ModuleSpecification object was not able to be created for {moduleSpecName}"; + var ex = new ArgumentException(exMessage); + var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + errorList.Add(ModuleSpecNotCreatedError); + moduleSpecCreatedSuccessfully = false; + continue; + } + } + else + { + // ModuleSpecification(Hashtable) constructor for when ModuleName + {Required,Maximum,Module}Version value is also provided + string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaximumVersion"] : String.Empty; + string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; + string moduleSpecRequiredVersion = moduleSpec.ContainsKey("RequiredVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; + Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; + + if (String.IsNullOrEmpty(moduleSpecMaxVersion) && String.IsNullOrEmpty(moduleSpecModuleVersion) && String.IsNullOrEmpty(moduleSpecRequiredVersion)) + { + var exMessage = $"ModuleSpecification hashtable requires one of the following keys: MaximumVersion, ModuleVersion, RequiredVersion and failed to be created for {moduleSpecName}"; + var ex = new ArgumentException(exMessage); + var MissingModuleSpecificationMemberError = new ErrorRecord(ex, "MissingModuleSpecificationMember", ErrorCategory.InvalidArgument, null); + errorList.Add(MissingModuleSpecificationMemberError); + moduleSpecCreatedSuccessfully = false; + continue; + } + + Hashtable moduleSpecHash = new Hashtable(); + + moduleSpecHash.Add("ModuleName", moduleSpecName); + if (moduleSpecGuid != Guid.Empty) + { + moduleSpecHash.Add("Guid", moduleSpecGuid); + } + + if (!String.IsNullOrEmpty(moduleSpecMaxVersion)) + { + moduleSpecHash.Add("MaximumVersion", moduleSpecMaxVersion); + } + + if (!String.IsNullOrEmpty(moduleSpecModuleVersion)) + { + moduleSpecHash.Add("ModuleVersion", moduleSpecModuleVersion); + } + + if (!String.IsNullOrEmpty(moduleSpecRequiredVersion)) + { + moduleSpecHash.Add("RequiredVersion", moduleSpecRequiredVersion); + } + + try + { + currentModuleSpec = new ModuleSpecification(moduleSpecHash); + } + catch (Exception e) + { + var ex = new ArgumentException($"ModuleSpecification instance was not able to be created with hashtable constructor due to: {e.Message}"); + var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + errorList.Add(ModuleSpecNotCreatedError); + moduleSpecCreatedSuccessfully = false; + } + + if (currentModuleSpec != null) + { + moduleSpecsList.Add(currentModuleSpec); + } + } + } + + errors = errorList.ToArray(); + validatedModuleSpecs = moduleSpecsList.ToArray(); + return moduleSpecCreatedSuccessfully; + } + #endregion #region Directory and File diff --git a/test/perf/benchmarks/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/test/perf/benchmarks/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 000000000..36203c728 --- /dev/null +++ b/test/perf/benchmarks/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfo.cs b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfo.cs new file mode 100644 index 000000000..334f4b01c --- /dev/null +++ b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("benchmarks")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("benchmarks")] +[assembly: System.Reflection.AssemblyTitleAttribute("benchmarks")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfoInputs.cache b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfoInputs.cache new file mode 100644 index 000000000..7f2a5ba55 --- /dev/null +++ b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +f62d892ae959bc3affd247abbde875daa919366d diff --git a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 000000000..6e6950d2d --- /dev/null +++ b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.PublishSingleFile = +build_property.IncludeAllContentForSelfExtract = +build_property._SupportedPlatformList = Android,iOS,Linux,macOS,Windows +build_property.RootNamespace = benchmarks +build_property.ProjectDir = C:\Users\annavied\Documents\PowerShellGet\test\perf\benchmarks\ diff --git a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.csproj.AssemblyReference.cache b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.csproj.AssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..68e2d1076569d4f97d3e9251e3d46e7757db5884 GIT binary patch literal 1039 zcmb`GK~LK-6vy*6G$B^lB(6j12Pk$!*|b&aVT6hUYE=ku@(ynD$RZqDN^B6CwC%#j z;WKcaG`;{5pQYGIipazPse6a#D7OFa_xH0$JBMKa0G{W;x1i6C3(O0~rR3)+#%xzb zCPS$UHc=Othu=|%J=CnoBNnL`*))}G7pEp+5_O@ujCme!k#2K6iG?U@Y+U{M>Eqq9 zt+?7ygx8k9#acC#xup7KKbL-6JUarOrULtv(x6XER60kckEpaEOf4#PsMMm;5|uVl zo9i8FS<8cAL#Wd}u}70CIp~ot@wZ90(+-T#CdYw9qjN4g?TML+G}_0@BXx#yAdMAs z+{-QjynRv`Ub&fP0-R$P&T+l>?~nD_?dJVK^#|ZXcv)!7$cRgxpz9F%YP8C@wt(eH zmn;8C&nf#ipHHqojX%zn-;a%LFsZ$!GJ!XC%CS0}r_3)=YgKB2$}-Efh|*f6G|81# zkrE4Oy{ol&bjFiKEw0Zk%F{G4dDVgc;MAR&eNM#!PF+u7<=kFX((Cs8F90nv9v+4D MGkv^)vpT?g0OQoln*aa+ literal 0 HcmV?d00001 diff --git a/test/perf/benchmarks/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/test/perf/benchmarks/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs new file mode 100644 index 000000000..36203c728 --- /dev/null +++ b/test/perf/benchmarks/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfo.cs b/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfo.cs new file mode 100644 index 000000000..57f696a63 --- /dev/null +++ b/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("benchmarks")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("benchmarks")] +[assembly: System.Reflection.AssemblyTitleAttribute("benchmarks")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfoInputs.cache b/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfoInputs.cache new file mode 100644 index 000000000..f11010da6 --- /dev/null +++ b/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +a9610066e961e220f7e3cf41437554fd26ebd535 diff --git a/test/perf/benchmarks/obj/Release/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig b/test/perf/benchmarks/obj/Release/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 000000000..6e6950d2d --- /dev/null +++ b/test/perf/benchmarks/obj/Release/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,10 @@ +is_global = true +build_property.TargetFramework = net6.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.PublishSingleFile = +build_property.IncludeAllContentForSelfExtract = +build_property._SupportedPlatformList = Android,iOS,Linux,macOS,Windows +build_property.RootNamespace = benchmarks +build_property.ProjectDir = C:\Users\annavied\Documents\PowerShellGet\test\perf\benchmarks\ From 3d718f81fe6b9381186ed96309cef4d62159e425 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 13 Jul 2022 22:01:53 -0400 Subject: [PATCH 02/29] revert commit to test perf bin and obj files --- ...CoreApp,Version=v6.0.AssemblyAttributes.cs | 4 --- .../Debug/net6.0/benchmarks.AssemblyInfo.cs | 23 ------------------ .../benchmarks.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 10 -------- .../benchmarks.csproj.AssemblyReference.cache | Bin 1039 -> 0 bytes ...CoreApp,Version=v6.0.AssemblyAttributes.cs | 4 --- .../Release/net6.0/benchmarks.AssemblyInfo.cs | 23 ------------------ .../benchmarks.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 10 -------- 9 files changed, 76 deletions(-) delete mode 100644 test/perf/benchmarks/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs delete mode 100644 test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfo.cs delete mode 100644 test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfoInputs.cache delete mode 100644 test/perf/benchmarks/obj/Debug/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 test/perf/benchmarks/obj/Debug/net6.0/benchmarks.csproj.AssemblyReference.cache delete mode 100644 test/perf/benchmarks/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs delete mode 100644 test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfo.cs delete mode 100644 test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfoInputs.cache delete mode 100644 test/perf/benchmarks/obj/Release/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig diff --git a/test/perf/benchmarks/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/test/perf/benchmarks/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs deleted file mode 100644 index 36203c728..000000000 --- a/test/perf/benchmarks/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfo.cs b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfo.cs deleted file mode 100644 index 334f4b01c..000000000 --- a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfo.cs +++ /dev/null @@ -1,23 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("benchmarks")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] -[assembly: System.Reflection.AssemblyProductAttribute("benchmarks")] -[assembly: System.Reflection.AssemblyTitleAttribute("benchmarks")] -[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfoInputs.cache b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfoInputs.cache deleted file mode 100644 index 7f2a5ba55..000000000 --- a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -f62d892ae959bc3affd247abbde875daa919366d diff --git a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 6e6950d2d..000000000 --- a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -is_global = true -build_property.TargetFramework = net6.0 -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = -build_property.ProjectTypeGuids = -build_property.PublishSingleFile = -build_property.IncludeAllContentForSelfExtract = -build_property._SupportedPlatformList = Android,iOS,Linux,macOS,Windows -build_property.RootNamespace = benchmarks -build_property.ProjectDir = C:\Users\annavied\Documents\PowerShellGet\test\perf\benchmarks\ diff --git a/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.csproj.AssemblyReference.cache b/test/perf/benchmarks/obj/Debug/net6.0/benchmarks.csproj.AssemblyReference.cache deleted file mode 100644 index 68e2d1076569d4f97d3e9251e3d46e7757db5884..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1039 zcmb`GK~LK-6vy*6G$B^lB(6j12Pk$!*|b&aVT6hUYE=ku@(ynD$RZqDN^B6CwC%#j z;WKcaG`;{5pQYGIipazPse6a#D7OFa_xH0$JBMKa0G{W;x1i6C3(O0~rR3)+#%xzb zCPS$UHc=Othu=|%J=CnoBNnL`*))}G7pEp+5_O@ujCme!k#2K6iG?U@Y+U{M>Eqq9 zt+?7ygx8k9#acC#xup7KKbL-6JUarOrULtv(x6XER60kckEpaEOf4#PsMMm;5|uVl zo9i8FS<8cAL#Wd}u}70CIp~ot@wZ90(+-T#CdYw9qjN4g?TML+G}_0@BXx#yAdMAs z+{-QjynRv`Ub&fP0-R$P&T+l>?~nD_?dJVK^#|ZXcv)!7$cRgxpz9F%YP8C@wt(eH zmn;8C&nf#ipHHqojX%zn-;a%LFsZ$!GJ!XC%CS0}r_3)=YgKB2$}-Efh|*f6G|81# zkrE4Oy{ol&bjFiKEw0Zk%F{G4dDVgc;MAR&eNM#!PF+u7<=kFX((Cs8F90nv9v+4D MGkv^)vpT?g0OQoln*aa+ diff --git a/test/perf/benchmarks/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs b/test/perf/benchmarks/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs deleted file mode 100644 index 36203c728..000000000 --- a/test/perf/benchmarks/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] diff --git a/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfo.cs b/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfo.cs deleted file mode 100644 index 57f696a63..000000000 --- a/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfo.cs +++ /dev/null @@ -1,23 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("benchmarks")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] -[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] -[assembly: System.Reflection.AssemblyProductAttribute("benchmarks")] -[assembly: System.Reflection.AssemblyTitleAttribute("benchmarks")] -[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfoInputs.cache b/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfoInputs.cache deleted file mode 100644 index f11010da6..000000000 --- a/test/perf/benchmarks/obj/Release/net6.0/benchmarks.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -a9610066e961e220f7e3cf41437554fd26ebd535 diff --git a/test/perf/benchmarks/obj/Release/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig b/test/perf/benchmarks/obj/Release/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 6e6950d2d..000000000 --- a/test/perf/benchmarks/obj/Release/net6.0/benchmarks.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -is_global = true -build_property.TargetFramework = net6.0 -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = -build_property.ProjectTypeGuids = -build_property.PublishSingleFile = -build_property.IncludeAllContentForSelfExtract = -build_property._SupportedPlatformList = Android,iOS,Linux,macOS,Windows -build_property.RootNamespace = benchmarks -build_property.ProjectDir = C:\Users\annavied\Documents\PowerShellGet\test\perf\benchmarks\ From 8fc841ecb11ee5097467c1cdcc086373f8e2fc27 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 14 Jul 2022 00:22:19 -0400 Subject: [PATCH 03/29] add requiredModules parse and validation single method --- src/code/PSScriptFileInfo2.cs | 21 +++------- src/code/PSScriptRequires.cs | 73 +++++++++++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/src/code/PSScriptFileInfo2.cs b/src/code/PSScriptFileInfo2.cs index e72d31b5d..d3f5c5f12 100644 --- a/src/code/PSScriptFileInfo2.cs +++ b/src/code/PSScriptFileInfo2.cs @@ -121,13 +121,11 @@ out ErrorRecord[] errors bool fileContentsSuccessfullyCreated = true; - // step 1: validate if (!ScriptMetadataCommment.ValidateContent(out ErrorRecord[] metadataValidationErrors)) { errorsList.AddRange(metadataValidationErrors); fileContentsSuccessfullyCreated = false; - } if (!ScriptHelpComment.ValidateContent(out ErrorRecord helpValidationError)) @@ -136,16 +134,12 @@ out ErrorRecord[] errors fileContentsSuccessfullyCreated = false; } - // if (!ScriptRequiresComment.ValidateContent()) - // { - // fileContentsSuccessfullyCreated = false; - // // store error and return - // // I think the method had a way of checking its not empty if it attemps to write - // } - // if (!ScriptContent.ValidateContent()) // { - + // todo: validate endofffilecontents here + // perhaps here ValidateContent will just check ContainsSignature is false. + // on Update cmdlet side, when we have PSScriptFileInfo.ScriptContent.ContainsSignature then can call + // PSScriptFileInfo.ScriptContent.RemoveSignature too. Otherwise will need to pass that param into this class. // } if (!fileContentsSuccessfullyCreated) @@ -154,9 +148,7 @@ out ErrorRecord[] errors return fileContentsSuccessfullyCreated; } - // todo: validate endofffilecontents here? - - // step: try to write + // step 2: create string that will be used to write later psScriptFileString = ScriptMetadataCommment.EmitContent(); string psRequiresCommentBlock = ScriptRequiresComment.EmitContent(); @@ -173,13 +165,10 @@ out ErrorRecord[] errors string psEndOfFileContent = ScriptContent.EmitContent(); if (!String.IsNullOrEmpty(psEndOfFileContent)) { - // todo: remove signature here? psScriptFileString += "\n" + psEndOfFileContent; } - return fileContentsSuccessfullyCreated; - } diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs index 36bc22821..e912d5466 100644 --- a/src/code/PSScriptRequires.cs +++ b/src/code/PSScriptRequires.cs @@ -42,18 +42,75 @@ public PSScriptRequires(ModuleSpecification[] requiredModules) #region Internal Methods - internal bool ParseContent() + /// + /// Parses RequiredModules out of comment lines and validates during + /// + internal bool ParseContent(string[] commentLines, out ErrorRecord[] errors) { - // use AST parser - return false; - } + /** + When Requires comment lines are obtained from .ps1 file they will have this format: - internal bool ValidateContent() - { - // use AST parser - return false; + #Requires -Module RequiredModule1 + #Requires -Module @{ ModuleName = 'RequiredModule2'; ModuleVersion = '2.0' } + #Requires -Module @{ ModuleName = 'RequiredModule3'; RequiredVersion = '2.5' } + #Requires -Module @{ ModuleName = 'RequiredModule4'; ModuleVersion = '1.1'; MaximumVersion = '2.0' } + #Requires -Module @{ ModuleName = 'RequiredModule5'; MaximumVersion = '1.5' } + + */ + + errors = new ErrorRecord[]{}; + List errorsList = new List(); + string requiresComment = String.Join("\n", commentLines); + + try + { + var ast = Parser.ParseInput( + requiresComment, + out Token[] tokens, + out ParseError[] parserErrors); + + if (parserErrors.Length > 0) + { + foreach (ParseError err in parserErrors) + { + var message = String.Format("Could not requires comments as valid PowerShell input due to {1}.", err.Message); + var ex = new InvalidOperationException(message); + var requiresCommentParseError = new ErrorRecord(ex, err.ErrorId, ErrorCategory.ParserError, null); + errorsList.Add(requiresCommentParseError); + } + + errors = errorsList.ToArray(); + return false; + } + + // get .REQUIREDMODULES property, by accessing the System.Management.Automation.Language.ScriptRequirements object ScriptRequirements.RequiredModules property + ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; + ReadOnlyCollection parsedModules = new List().AsReadOnly(); + + if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) + { + RequiredModules = parsedScriptRequirements.RequiredModules.ToArray(); + } + } + catch (Exception e) + { + var message = $"Parsing RequiredModules failed due to {e.Message}"; + var ex = new ArgumentException(message); + var requiredModulesAstParseError = new ErrorRecord(ex, "requiredModulesAstParseThrewError", ErrorCategory.ParserError, null); + errorsList.Add(requiredModulesAstParseError); + errors = errorsList.ToArray(); + return false; + } + + return true; } + // internal bool ValidateContent() + // { + // // use AST parser + // return false; + // } + internal string EmitContent() { if (RequiredModules.Length > 0) From 4815e221887d40b24afaa557ce0950fcb51c3775 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 14 Jul 2022 00:48:19 -0400 Subject: [PATCH 04/29] push code for Test-PSScriptFile --- src/code/PSScriptContents.cs | 12 ++- src/code/PSScriptFileInfo2.cs | 156 ++++++++++++++++++++++++++++++++-- src/code/PSScriptHelp.cs | 2 + src/code/PSScriptRequires.cs | 2 + 4 files changed, 159 insertions(+), 13 deletions(-) diff --git a/src/code/PSScriptContents.cs b/src/code/PSScriptContents.cs index e0b6cf0d7..fb001ea31 100644 --- a/src/code/PSScriptContents.cs +++ b/src/code/PSScriptContents.cs @@ -49,15 +49,19 @@ public PSScriptContents(string endOfFileContents) this.ContainsSignature = CheckForSignature(); } + internal PSScriptContents() {} + #endregion #region Public Methods - public void ParseContent(string[] commentLines, out ErrorRecord[] errors, bool removeSignature) + public void ParseContent(string[] commentLines) { - errors = null; - EndOfFileContents = String.Join("", commentLines); - ContainsSignature = CheckForSignature(); + if (commentLines.Length != 0) + { + EndOfFileContents = String.Join("", commentLines); + ContainsSignature = CheckForSignature(); + } } /// diff --git a/src/code/PSScriptFileInfo2.cs b/src/code/PSScriptFileInfo2.cs index d3f5c5f12..bc6c791aa 100644 --- a/src/code/PSScriptFileInfo2.cs +++ b/src/code/PSScriptFileInfo2.cs @@ -62,7 +62,6 @@ public PSScriptFileInfo2( licenseUri, projectUri, iconUri, - // requiredModules, externalModuleDependencies, requiredScripts, externalScriptDependencies, @@ -107,6 +106,153 @@ out string[] msgs parsedScript = null; errors = null; msgs = new string[]{}; + + + + + return true; + } + + internal static bool TryParseScriptFile2( + string scriptFileInfoPath, + // out Hashtable parsedScriptMetadata, + out PSScriptFileInfo2 parsedScript, + out ErrorRecord error + ) + { + error = null; + parsedScript = null; + + string[] fileContents = File.ReadAllLines(scriptFileInfoPath); + + List psScriptInfoCommentContent = new List(); + List helpInfoCommentContent = new List(); + List requiresContent = new List(); + + + + PSScriptContents currentScriptContents; + PSScriptRequires currentRequiresComment; + + string[] remainingFileContentArray = new string[]{}; + + bool gotEndToPSSCriptInfoContent = false; + bool gotEndToHelpInfoContent = false; + + int i = 0; + int endOfFileContentsStartIndex = 0; + while (i < fileContents.Length) + { + string line = fileContents[i]; + + if (line.StartsWith("<#PSScriptInfo")) + { + int j = i + 1; // start at the next line + // keep grabbing lines until we get to closing #> + while (j < fileContents.Length) + { + string blockLine = fileContents[j]; + if (blockLine.StartsWith("#>")) + { + gotEndToPSSCriptInfoContent = true; + i = j + 1; + break; + } + + psScriptInfoCommentContent.Add(blockLine); + j++; + } + + if (!gotEndToPSSCriptInfoContent) + { + var message = String.Format("Could not parse '{0}' as a PowerShell script file due to missing the closing '#>' for <#PSScriptInfo comment block", scriptFileInfoPath); + var ex = new InvalidOperationException(message); + error = new ErrorRecord(ex, "MissingEndBracketToPSScriptInfoParseError", ErrorCategory.ParserError, null); + return false; + } + } + else if (line.StartsWith("<#")) + { + // we assume the next comment block should be the help comment block (containing description) + // keep grabbing lines until we get to closing #> + int j = i + 1; + while (j < fileContents.Length) + { + string blockLine = fileContents[j]; + if (blockLine.StartsWith("#>")) + { + gotEndToHelpInfoContent = true; + i = j + 1; + endOfFileContentsStartIndex = i; + break; + } + + helpInfoCommentContent.Add(blockLine); + j++; + } + + if (!gotEndToHelpInfoContent) + { + var message = String.Format("Could not parse '{0}' as a PowerShell script file due to missing the closing '#>' for HelpInfo comment block", scriptFileInfoPath); + var ex = new InvalidOperationException(message); + error = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); + return false; + } + } + else if (line.StartsWith("#Requires")) + { + requiresContent.Add(line); + i++; + } + else if (endOfFileContentsStartIndex != 0) + { + break; + } + else + { + // this would be newlines between blocks, or if there was other (unexpected) data between PSScriptInfo, Requires, and HelpInfo blocks + i++; + } + } + + if (endOfFileContentsStartIndex != 0 && (endOfFileContentsStartIndex < fileContents.Length)) + { + // from this line to fileContents.Length is the endOfFileContents + // save it to append to end of file during Update + remainingFileContentArray = new string[fileContents.Length - endOfFileContentsStartIndex]; + Array.Copy(fileContents, endOfFileContentsStartIndex, remainingFileContentArray, 0, (fileContents.Length - endOfFileContentsStartIndex)); + } + + + // now populate PSScriptFileInfo object + // first create instances for the property objects + + PSScriptMetadata currentMetadata = new PSScriptMetadata(); + if (!currentMetadata.ParseContentIntoObj(commentLines: psScriptInfoCommentContent.ToArray(), + out ErrorRecord[] metadataErrors, + out string[] verboseMsgs)) + { + // set errors and return false + // also perhaps verbose msgs? + } + + PSScriptHelp currentHelpInfo = new PSScriptHelp(); + if (!currentHelpInfo.ParseContentIntoObj(commentLines: helpInfoCommentContent.ToArray())) + { + // write error + // todo: why doesn't this return error? maybe parse level validation? + } + + PSScriptRequires requiresComment = new PSScriptRequires(); + if (!requiresComment.ParseContent(commentLines: requiresContent.ToArray(), + out ErrorRecord[] requiresErrors)) + { + // set errors and return false + } + + PSScriptContents endOfFileContents = new PSScriptContents(); + endOfFileContents.ParseContent(commentLines: remainingFileContentArray); + return true; } @@ -183,14 +329,6 @@ out ErrorRecord[] errors return true; } - - - - - - #endregion - - } } diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs index 6829769ba..e12d9fbe8 100644 --- a/src/code/PSScriptHelp.cs +++ b/src/code/PSScriptHelp.cs @@ -106,6 +106,8 @@ public PSScriptHelp ( this.Functionality = functionality; } + internal PSScriptHelp() {} + #endregion #region Internal Methods diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs index e912d5466..0ab447217 100644 --- a/src/code/PSScriptRequires.cs +++ b/src/code/PSScriptRequires.cs @@ -38,6 +38,8 @@ public PSScriptRequires(ModuleSpecification[] requiredModules) this.RequiredModules = requiredModules ?? new ModuleSpecification[]{}; } + internal PSScriptRequires() {} + #endregion #region Internal Methods From 2a910697e113f60f5ca71d5bbc08f69d25d4d9cb Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 14 Jul 2022 15:30:31 -0400 Subject: [PATCH 05/29] add methods to validate parsed content, all tests for Test-PSScriptFileInfo now pass --- src/PowerShellGet.psd1 | 3 +- ...iptFileInfo2.cs => NewPSScriptFileInfo.cs} | 6 +- ...ScriptFileInfo2.cs => PSScriptFileInfo.cs} | 96 ++--- src/code/PSScriptHelp.cs | 39 +- src/code/PSScriptMetadata.cs | 58 ++- src/code/PSScriptRequires.cs | 8 +- src/code/TestPSScriptFileInfo.cs | 88 +++++ src/code/UpdatePSScriptFileInfo.cs | 322 ++++++++++++++++ test/NewPSScriptFileInfo.Tests.ps1 | 356 ++++++++---------- test/TestPSScriptFileInfo.Tests.ps1 | 60 +++ test/TestPSScriptInfo.Tests.ps1 | 42 --- 11 files changed, 760 insertions(+), 318 deletions(-) rename src/code/{NewPSScriptFileInfo2.cs => NewPSScriptFileInfo.cs} (98%) rename src/code/{PSScriptFileInfo2.cs => PSScriptFileInfo.cs} (74%) create mode 100644 src/code/TestPSScriptFileInfo.cs create mode 100644 src/code/UpdatePSScriptFileInfo.cs create mode 100644 test/TestPSScriptFileInfo.Tests.ps1 delete mode 100644 test/TestPSScriptInfo.Tests.ps1 diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index d35878d98..6500e0e49 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -21,7 +21,8 @@ 'Register-PSResourceRepository', 'Save-PSResource', 'Set-PSResourceRepository', - 'New-PSScriptFileInfo2' + 'New-PSScriptFileInfo', + 'Test-PSScriptFileInfo', 'Publish-PSResource', 'Uninstall-PSResource', 'Unregister-PSResourceRepository', diff --git a/src/code/NewPSScriptFileInfo2.cs b/src/code/NewPSScriptFileInfo.cs similarity index 98% rename from src/code/NewPSScriptFileInfo2.cs rename to src/code/NewPSScriptFileInfo.cs index 7bfa3a5dd..830fad49e 100644 --- a/src/code/NewPSScriptFileInfo2.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -13,8 +13,8 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets /// /// Creates a new .ps1 file with script information required for publishing a script. /// - [Cmdlet(VerbsCommon.New, "PSScriptFileInfo2")] - public sealed class NewPSScriptFileInfo2 : PSCmdlet + [Cmdlet(VerbsCommon.New, "PSScriptFileInfo")] + public sealed class NewPSScriptFileInfo : PSCmdlet { #region Parameters @@ -220,7 +220,7 @@ protected override void EndProcessing() } } - PSScriptFileInfo2 scriptInfo = new PSScriptFileInfo2( + PSScriptFileInfo scriptInfo = new PSScriptFileInfo( version: Version, guid: Guid, author: Author, diff --git a/src/code/PSScriptFileInfo2.cs b/src/code/PSScriptFileInfo.cs similarity index 74% rename from src/code/PSScriptFileInfo2.cs rename to src/code/PSScriptFileInfo.cs index bc6c791aa..8bd7f46cf 100644 --- a/src/code/PSScriptFileInfo2.cs +++ b/src/code/PSScriptFileInfo.cs @@ -19,7 +19,7 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses /// /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents). /// - public sealed class PSScriptFileInfo2 + public sealed class PSScriptFileInfo { #region Properties public PSScriptMetadata ScriptMetadataCommment { get; set; } @@ -34,7 +34,7 @@ public sealed class PSScriptFileInfo2 #region Constructor - public PSScriptFileInfo2( + public PSScriptFileInfo( string version, Guid guid, string author, @@ -78,7 +78,7 @@ public PSScriptFileInfo2( this.ScriptContent = scriptRemainingContent; } - public PSScriptFileInfo2( + public PSScriptFileInfo( PSScriptMetadata scriptMetadataComment, PSScriptHelp scriptHelpComment, PSScriptRequires scriptRequiresComment, @@ -97,7 +97,7 @@ PSScriptContents scriptRemainingContent internal static bool TryParseScriptFile( string scriptFileInfoPath, - out PSScriptFileInfo2 parsedScript, + out PSScriptFileInfo parsedScript, out ErrorRecord[] errors, out string[] msgs ) @@ -115,12 +115,13 @@ out string[] msgs internal static bool TryParseScriptFile2( string scriptFileInfoPath, - // out Hashtable parsedScriptMetadata, - out PSScriptFileInfo2 parsedScript, - out ErrorRecord error + out PSScriptFileInfo parsedScript, + out ErrorRecord[] errors, + out string[] verboseMsgs // this is for Uri errors, which aren't required by script but we check if those in the script aren't valid Uri's. ) { - error = null; + verboseMsgs = new string[]{}; + List errorsList = new List(); parsedScript = null; string[] fileContents = File.ReadAllLines(scriptFileInfoPath); @@ -128,16 +129,11 @@ out ErrorRecord error List psScriptInfoCommentContent = new List(); List helpInfoCommentContent = new List(); List requiresContent = new List(); - - - - PSScriptContents currentScriptContents; - PSScriptRequires currentRequiresComment; - string[] remainingFileContentArray = new string[]{}; bool gotEndToPSSCriptInfoContent = false; bool gotEndToHelpInfoContent = false; + bool parsedContentSuccessfully = true; int i = 0; int endOfFileContentsStartIndex = 0; @@ -167,7 +163,8 @@ out ErrorRecord error { var message = String.Format("Could not parse '{0}' as a PowerShell script file due to missing the closing '#>' for <#PSScriptInfo comment block", scriptFileInfoPath); var ex = new InvalidOperationException(message); - error = new ErrorRecord(ex, "MissingEndBracketToPSScriptInfoParseError", ErrorCategory.ParserError, null); + var missingEndBracketToPSScriptInfoParseError = new ErrorRecord(ex, "MissingEndBracketToPSScriptInfoParseError", ErrorCategory.ParserError, null); + errors = new ErrorRecord[]{missingEndBracketToPSScriptInfoParseError}; return false; } } @@ -195,7 +192,8 @@ out ErrorRecord error { var message = String.Format("Could not parse '{0}' as a PowerShell script file due to missing the closing '#>' for HelpInfo comment block", scriptFileInfoPath); var ex = new InvalidOperationException(message); - error = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); + var missingEndBracketToHelpInfoCommentParseError = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); + errors = new ErrorRecord[]{missingEndBracketToHelpInfoCommentParseError}; return false; } } @@ -217,43 +215,65 @@ out ErrorRecord error if (endOfFileContentsStartIndex != 0 && (endOfFileContentsStartIndex < fileContents.Length)) { - // from this line to fileContents.Length is the endOfFileContents - // save it to append to end of file during Update + // from this line to fileContents.Length is the endOfFileContents, if any remainingFileContentArray = new string[fileContents.Length - endOfFileContentsStartIndex]; Array.Copy(fileContents, endOfFileContentsStartIndex, remainingFileContentArray, 0, (fileContents.Length - endOfFileContentsStartIndex)); } + if (psScriptInfoCommentContent.Count() == 0) + { + // check for file not containing '<#PSScriptInfo ... #>' comment + var message = String.Format("Could not parse '{0}' as a PowerShell script due to it missing '<#PSScriptInfo #> block", scriptFileInfoPath); + var ex = new InvalidOperationException(message); + var missingPSScriptInfoCommentError = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); + errors = new ErrorRecord[]{missingPSScriptInfoCommentError}; + return false; + } - // now populate PSScriptFileInfo object - // first create instances for the property objects + if (helpInfoCommentContent.Count() == 0) + { + // check for file not containing HelpInfo comment + var message = String.Format("Could not parse '{0}' as a PowerShell script due to it missing HelpInfo comment block", scriptFileInfoPath); + var ex = new InvalidOperationException(message); + var missingHelpInfoCommentError = new ErrorRecord(ex, "missingHelpInfoCommentError", ErrorCategory.ParserError, null); + errors = new ErrorRecord[]{missingHelpInfoCommentError}; + return false; + } + // now populate PSScriptFileInfo object by first creating instances for the property objects PSScriptMetadata currentMetadata = new PSScriptMetadata(); - if (!currentMetadata.ParseContentIntoObj(commentLines: psScriptInfoCommentContent.ToArray(), + if (!currentMetadata.ParseContentIntoObj( + commentLines: psScriptInfoCommentContent.ToArray(), out ErrorRecord[] metadataErrors, - out string[] verboseMsgs)) + out verboseMsgs)) { - // set errors and return false - // also perhaps verbose msgs? + errorsList.AddRange(metadataErrors); + parsedContentSuccessfully = false; } PSScriptHelp currentHelpInfo = new PSScriptHelp(); - if (!currentHelpInfo.ParseContentIntoObj(commentLines: helpInfoCommentContent.ToArray())) + if (!currentHelpInfo.ParseContentIntoObj( + commentLines: helpInfoCommentContent.ToArray(), + out ErrorRecord helpError)) { - // write error - // todo: why doesn't this return error? maybe parse level validation? + errorsList.Add(helpError); + parsedContentSuccessfully = false; } - PSScriptRequires requiresComment = new PSScriptRequires(); - if (!requiresComment.ParseContent(commentLines: requiresContent.ToArray(), + PSScriptRequires currentRequiresComment = new PSScriptRequires(); + if (!currentRequiresComment.ParseContentIntoObj( + commentLines: requiresContent.ToArray(), out ErrorRecord[] requiresErrors)) { - // set errors and return false + errorsList.AddRange(requiresErrors); + parsedContentSuccessfully = false; } - PSScriptContents endOfFileContents = new PSScriptContents(); - endOfFileContents.ParseContent(commentLines: remainingFileContentArray); + PSScriptContents currentEndOfFileContents = new PSScriptContents(); + currentEndOfFileContents.ParseContent(commentLines: remainingFileContentArray); - return true; + errors = errorsList.ToArray(); + return parsedContentSuccessfully; } internal bool TryCreateScriptFileInfoString( @@ -280,14 +300,6 @@ out ErrorRecord[] errors fileContentsSuccessfullyCreated = false; } - // if (!ScriptContent.ValidateContent()) - // { - // todo: validate endofffilecontents here - // perhaps here ValidateContent will just check ContainsSignature is false. - // on Update cmdlet side, when we have PSScriptFileInfo.ScriptContent.ContainsSignature then can call - // PSScriptFileInfo.ScriptContent.RemoveSignature too. Otherwise will need to pass that param into this class. - // } - if (!fileContentsSuccessfullyCreated) { errors = errorsList.ToArray(); @@ -319,7 +331,7 @@ out ErrorRecord[] errors internal static bool TryUpdateScriptFileContents( - PSScriptFileInfo2 scriptInfo, + PSScriptFileInfo scriptInfo, out string updatedPSScriptFileContents, out ErrorRecord[] errors ) diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs index e12d9fbe8..fb4cfc4b5 100644 --- a/src/code/PSScriptHelp.cs +++ b/src/code/PSScriptHelp.cs @@ -112,18 +112,22 @@ internal PSScriptHelp() {} #region Internal Methods - internal bool ParseContentIntoObj(string[] commentLines) + internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord error) { - bool successfullyParsed = false; + bool successfullyParsed = true; char[] spaceDelimeter = new char[]{' '}; char[] newlineDelimeter = new char[]{'\n'}; // parse content into a hashtable Hashtable parsedHelpMetadata = ParseContent(commentLines); + + if (!ValidateParsedContent(parsedHelpMetadata, out error)) + { + return false; + } // populate object - Description = (string) parsedHelpMetadata["DESCRIPTION"] ?? String.Empty; - + Description = (string) parsedHelpMetadata["DESCRIPTION"]; Synopsis = (string) parsedHelpMetadata["SYNOPSIS"] ?? String.Empty; Example = Utils.GetStringArrayFromString(newlineDelimeter, (string) parsedHelpMetadata["EXAMPLE"]); Inputs = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["INPUT"]); @@ -133,9 +137,6 @@ internal bool ParseContentIntoObj(string[] commentLines) Component = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["COMPONENT"]); Role = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["ROLE"]); Functionality = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["FUNCTIONALITY"]); - - // TODO: validate obj here? - return successfullyParsed; } @@ -190,6 +191,30 @@ internal Hashtable ParseContent(string[] commentLines) return parsedHelpMetadata; } + internal bool ValidateParsedContent(Hashtable parsedHelpMetadata, out ErrorRecord error) + { + error = null; + if (!parsedHelpMetadata.ContainsKey("DESCRIPTION") || String.IsNullOrEmpty((string) parsedHelpMetadata["DESCRIPTION"]) || String.Equals(((string) parsedHelpMetadata["DESCRIPTION"]).Trim(), String.Empty)) + { + var exMessage = "PSScript file must contain value for Description. Ensure value for Description is passed in and try again."; + var ex = new ArgumentException(exMessage); + var PSScriptInfoMissingDescriptionError = new ErrorRecord(ex, "PSScriptInfoMissingDescription", ErrorCategory.InvalidArgument, null); + error = PSScriptInfoMissingDescriptionError; + return false; + } + + if (StringContainsComment((string) parsedHelpMetadata["DESCRIPTION"])) + { + var exMessage = "PSScript file's value for Description cannot contain '<#' or '#>'. Pass in a valid value for Description and try again."; + var ex = new ArgumentException(exMessage); + var DescriptionContainsCommentError = new ErrorRecord(ex, "DescriptionContainsComment", ErrorCategory.InvalidArgument, null); + error = DescriptionContainsCommentError; + return false; + } + + return true; + } + internal bool ValidateContent(out ErrorRecord error) { error = null; diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs index cb24b71ce..1339cc6d9 100644 --- a/src/code/PSScriptMetadata.cs +++ b/src/code/PSScriptMetadata.cs @@ -149,17 +149,26 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error { msgs = new string[]{}; List msgsList = new List(); - errors = new ErrorRecord[]{}; // parse content into a hashtable Hashtable parsedMetadata = ParseContent(commentLines); - // // and then validate contents on the parsed content (for required Author, Version, Guid metadata) - // if (!ValidateContent(out errors)) - // { - // return false; - // } + if (parsedMetadata.Count == 0) + { + var message = String.Format("PowerShell script '<#PSScriptInfo .. #>' comment block contains no metadata"); + var ex = new InvalidOperationException(message); + var psScriptInfoBlockMissingMetadata = new ErrorRecord(ex, "psScriptInfoBlockMissingMetadataError", ErrorCategory.ParserError, null); + errors = new ErrorRecord[]{psScriptInfoBlockMissingMetadata}; + return false; + } + + // check parsed metadata contains required Author, Version, Guid key values + if (!ValidateParsedContent(parsedMetadata, out errors)) + { + return false; + } + // now populate the object instance char[] spaceDelimeter = new char[]{' '}; Uri parsedLicenseUri = null; @@ -206,15 +215,14 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error RequiredScripts = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedMetadata["REQUIREDSCRIPTS"]); ExternalScriptDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedMetadata["EXTERNALSCRIPTDEPENDENCIES"]); ReleaseNotes = (string) parsedMetadata["RELEASENOTES"] ?? String.Empty; - PrivateData = (string) parsedMetadata["PRIVATEDATA"] ?? String.Empty; - + PrivateData = (string) parsedMetadata["PRIVATEDATA"] ?? String.Empty; msgs = msgsList.ToArray(); return true; } /// - /// Parses metadata out of of comment block's lines (which are passed in) into a hashtable + /// Helper method that parses metadata out of of comment block's lines (which are passed in) into a hashtable /// internal Hashtable ParseContent(string[] commentLines) { @@ -302,6 +310,38 @@ some data return parsedMetadata; } + + internal bool ValidateParsedContent(Hashtable parsedMetadata, out ErrorRecord[] errors) + { + List errorsList = new List(); + + if (!parsedMetadata.ContainsKey("VERSION") || String.IsNullOrEmpty((string) parsedMetadata["VERSION"]) || String.Equals(((string) parsedMetadata["VERSION"]).Trim(), String.Empty)) + { + var message = String.Format("PSScript file is missing the required Version property"); + var ex = new ArgumentException(message); + var psScriptMissingVersionError = new ErrorRecord(ex, "psScriptMissingVersion", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingVersionError); + } + + if (!parsedMetadata.ContainsKey("AUTHOR") || String.IsNullOrEmpty((string) parsedMetadata["AUTHOR"]) || String.Equals(((string) parsedMetadata["AUTHOR"]).Trim(), String.Empty)) + { + var message = String.Format("PSScript file is missing the required Author property"); + var ex = new ArgumentException(message); + var psScriptMissingAuthorError = new ErrorRecord(ex, "psScriptMissingAuthor", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingAuthorError); + } + + if (!parsedMetadata.ContainsKey("GUID") || String.IsNullOrEmpty((string) parsedMetadata["GUID"]) || String.Equals(((string) parsedMetadata["GUID"]).Trim(), String.Empty)) + { + var message = String.Format("PSScript file is missing the required Guid property"); + var ex = new ArgumentException(message); + var psScriptMissingGuidError = new ErrorRecord(ex, "psScriptMissingGuid", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingGuidError); + } + + errors = errorsList.ToArray(); + return errors.Length == 0; + } /// /// Validates metadata content parsed from .ps1 is valid and contains required script properties /// i.e Author, Version, Guid diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs index 0ab447217..9becfdff8 100644 --- a/src/code/PSScriptRequires.cs +++ b/src/code/PSScriptRequires.cs @@ -47,7 +47,7 @@ internal PSScriptRequires() {} /// /// Parses RequiredModules out of comment lines and validates during /// - internal bool ParseContent(string[] commentLines, out ErrorRecord[] errors) + internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] errors) { /** When Requires comment lines are obtained from .ps1 file they will have this format: @@ -107,12 +107,6 @@ internal bool ParseContent(string[] commentLines, out ErrorRecord[] errors) return true; } - // internal bool ValidateContent() - // { - // // use AST parser - // return false; - // } - internal string EmitContent() { if (RequiredModules.Length > 0) diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs new file mode 100644 index 000000000..997508531 --- /dev/null +++ b/src/code/TestPSScriptFileInfo.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Management.Automation; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// Tests the contents of a .ps1 file to see if it has all properties and is in correct format + /// for publishing the script with the file. + /// + [Cmdlet(VerbsDiagnostic.Test, "PSScriptFileInfo")] + [OutputType(typeof(bool))] + public sealed class TestPSScriptFileInfo : PSCmdlet + { + #region Parameters + + /// + /// The path to the .ps1 file to test. + /// + [Parameter(Position = 0, Mandatory = true)] + [ValidateNotNullOrEmpty] + public string FilePath { get; set; } + + #endregion + + #region Methods + + protected override void EndProcessing() + { + if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + { + var exMessage = "Path needs to end with a .ps1 file. Example: C:/Users/john/x/MyScript.ps1"; + var ex = new ArgumentException(exMessage); + var InvalidPathError = new ErrorRecord(ex, "InvalidPath", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InvalidPathError); + } + + var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); + if (resolvedPaths.Count != 1) + { + var exMessage = "Error: Could not resolve provided Path argument into a single path."; + var ex = new PSArgumentException(exMessage); + var InvalidPathArgumentError = new ErrorRecord(ex, "InvalidPathArgumentError", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InvalidPathArgumentError); + } + + var resolvedFilePath = resolvedPaths[0].Path; + + if (!File.Exists(resolvedFilePath)) + { + var exMessage = "A .ps1 file does not exist at the location specified."; + var ex = new ArgumentException(exMessage); + var FileDoesNotExistError = new ErrorRecord(ex, "FileDoesNotExistAtPath", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(FileDoesNotExistError); + } + + bool isValidScript = PSScriptFileInfo.TryParseScriptFile2( + scriptFileInfoPath: resolvedFilePath, + parsedScript: out PSScriptFileInfo parsedScriptInfo, + errors: out ErrorRecord[] errors, + out string[] verboseMsgs); + + if (!isValidScript) + { + foreach (ErrorRecord error in errors) + { + WriteWarning("The .ps1 script file passed in was not valid due to: " + error.Exception.Message); + } + } + + foreach (string msg in verboseMsgs) + { + WriteVerbose(msg); + + // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. + WriteWarning(msg); + } + + WriteObject(isValidScript); + } + + #endregion + } +} diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs new file mode 100644 index 000000000..3408ee49b --- /dev/null +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -0,0 +1,322 @@ +// // Copyright (c) Microsoft Corporation. All rights reserved. +// // Licensed under the MIT License. + +// using System; +// using System.Collections; +// using System.IO; +// using System.Management.Automation; +// using Microsoft.PowerShell.Commands; +// using Microsoft.PowerShell.PowerShellGet.UtilClasses; + +// namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +// { +// /// +// /// Updates a .ps1 file with specified properties. +// /// +// [Cmdlet(VerbsData.Update, "PSScriptFileInfo")] +// public sealed class UpdatePSScriptFileInfo : PSCmdlet +// { +// #region Parameters + +// /// +// /// The author of the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string Author { get; set; } + +// /// +// /// The name of the company owning the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string CompanyName { get; set; } + +// /// +// /// The copyright statement for the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string Copyright { get; set; } + +// /// +// /// The description of the script. +// /// +// [Parameter()] +// [ValidateNotNullOrEmpty()] +// public string Description { get; set; } + +// /// +// /// The list of external module dependencies taken by this script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string[] ExternalModuleDependencies { get; set; } + +// /// +// /// The list of external script dependencies taken by this script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string[] ExternalScriptDependencies { get; set; } + +// /// +// /// The unique identifier for the script. The GUID can be used to distinguish among scripts with the same name. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public Guid Guid { get; set; } + +// /// +// /// The Uri for the icon associated with the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string IconUri { get; set; } + +// /// +// /// The Uri for the license associated with the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string LicenseUri { get; set; } + +// /// +// /// The path the .ps1 script info file will be created at. +// /// +// [Parameter(Position = 0, Mandatory = true)] +// [ValidateNotNullOrEmpty] +// public string FilePath { get; set; } + +// /// +// /// The private data associated with the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string PrivateData { get; set; } + +// /// +// /// The Uri for the project associated with the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string ProjectUri { get; set; } + +// /// +// /// The release notes for the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string ReleaseNotes { get; set; } + +// /// +// /// Remove signature from signed .ps1 (if present) thereby allowing update of script to happen +// /// User should re-sign the updated script afterwards. +// /// +// [Parameter] +// public SwitchParameter RemoveSignature { get; set; } + +// /// +// /// The list of modules required by the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public Hashtable[] RequiredModules { get; set; } + +// /// +// /// The list of scripts required by the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string[] RequiredScripts { get; set; } + +// /// +// /// The tags associated with the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string[] Tags { get; set; } + +// /// +// /// The version of the script. +// /// +// [Parameter] +// [ValidateNotNullOrEmpty()] +// public string Version { get; set; } + +// #endregion + +// #region Private Members + +// private const string signatureStartString = "# SIG # Begin signature block"; + +// #endregion + +// #region Methods + +// protected override void EndProcessing() +// { +// Uri projectUri = null; +// if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri, +// cmdletPassedIn: this, +// uriResult: out projectUri, +// errorRecord: out ErrorRecord projectErrorRecord)) +// { +// ThrowTerminatingError(projectErrorRecord); +// } + +// Uri licenseUri = null; +// if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUri(uriString: LicenseUri, +// cmdletPassedIn: this, +// uriResult: out licenseUri, +// errorRecord: out ErrorRecord licenseErrorRecord)) +// { +// ThrowTerminatingError(licenseErrorRecord); +// } + +// Uri iconUri = null; +// if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUri(uriString: IconUri, +// cmdletPassedIn: this, +// uriResult: out iconUri, +// errorRecord: out ErrorRecord iconErrorRecord)) +// { +// ThrowTerminatingError(iconErrorRecord); +// } + +// if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) +// { +// var exMessage = "File path needs to end with a .ps1 extension. Example: C:/Users/john/x/MyScript.ps1"; +// var ex = new ArgumentException(exMessage); +// var InvalidOrNonExistantPathError = new ErrorRecord(ex, "InvalidOrNonExistantPath", ErrorCategory.InvalidArgument, null); +// ThrowTerminatingError(InvalidOrNonExistantPathError); +// } + +// var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); +// if (resolvedPaths.Count != 1) +// { +// var exMessage = "Error: Could not resolve provided Path argument into a single path."; +// var ex = new PSArgumentException(exMessage); +// var InvalidPathArgumentError = new ErrorRecord(ex, "InvalidPathArgumentError", ErrorCategory.InvalidArgument, null); +// ThrowTerminatingError(InvalidPathArgumentError); +// } + +// string resolvedFilePath = resolvedPaths[0].Path; + +// if (!File.Exists(resolvedFilePath)) +// { +// var exMessage = "A script file does not exist at the location specified"; +// var ex = new ArgumentException(exMessage); +// var FileDoesNotExistError = new ErrorRecord(ex, "FileDoesNotExistAtPath", ErrorCategory.InvalidArgument, null); +// ThrowTerminatingError(FileDoesNotExistError); +// } + +// ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; +// if (RequiredModules != null && RequiredModules.Length > 0) +// { +// if (!Utils.TryCreateModuleSpecification( +// moduleSpecHashtables: RequiredModules, +// out validatedRequiredModuleSpecifications, +// out ErrorRecord[] moduleSpecErrors)) +// { +// foreach (ErrorRecord err in moduleSpecErrors) +// { +// WriteError(err); +// } + +// return; +// } +// } + +// if (!PSScriptFileInfo.TryParseScriptIntoPSScriptInfo( +// scriptFileInfoPath: resolvedFilePath, +// parsedScript: out PSScriptFileInfo parsedScriptInfo, +// errors: out ErrorRecord[] errors, +// out string[] verboseMsgs)) +// { +// foreach (string msg in verboseMsgs) +// { +// WriteVerbose(msg); + +// // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. +// WriteWarning(msg); +// } + +// WriteWarning("The .ps1 script file passed in was not valid due to the following error(s) listed below"); +// foreach (ErrorRecord error in errors) +// { +// WriteError(error); +// } + +// return; +// } + +// if (parsedScriptInfo.EndOfFileContents.Contains(signatureStartString)) +// { +// WriteWarning("This script contains a signature and cannot be updated without invalidating the current script signature"); +// if (!RemoveSignature) +// { +// var exMessage = "Cannot update the script file because the file contains a signature block and updating will invalidate the signature. Use -RemoveSignature to remove the signature block, and then re-sign the file after it is updated."; +// var ex = new PSInvalidOperationException(exMessage); +// var ScriptToBeUpdatedContainsSignatureError = new ErrorRecord(ex, "ScriptToBeUpdatedContainsSignature", ErrorCategory.InvalidOperation, null); +// ThrowTerminatingError(ScriptToBeUpdatedContainsSignatureError); +// } +// } + +// if (!PSScriptFileInfo.TryUpdateScriptFileContents( +// scriptInfo: parsedScriptInfo, +// updatedPSScriptFileContents: out string updatedPSScriptFileContents, +// errors: out ErrorRecord[] updateErrors, +// version: Version, +// guid: Guid, +// author: Author, +// companyName: CompanyName, +// copyright: Copyright, +// tags: Tags, +// licenseUri: licenseUri, +// projectUri: projectUri, +// iconUri: iconUri, +// requiredModules: validatedRequiredModuleSpecifications, +// externalModuleDependencies: ExternalModuleDependencies, +// requiredScripts: RequiredScripts, +// externalScriptDependencies: ExternalScriptDependencies, +// releaseNotes: ReleaseNotes, +// privateData: PrivateData, +// description: Description)) +// { +// WriteWarning("Updating the specified script file failed due to the following error(s):"); +// foreach (ErrorRecord error in updateErrors) +// { +// WriteError(error); +// } + +// return; +// } + +// string tempScriptFilePath = null; +// try +// { +// tempScriptFilePath = Path.GetTempFileName(); + +// File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); +// File.Copy(tempScriptFilePath, resolvedFilePath, overwrite: true); +// } +// catch(Exception e) +// { +// WriteError(new ErrorRecord( +// new PSInvalidOperationException($"Could not update .ps1 file due to: {e.Message}"), +// "FileIOErrorDuringUpdate", +// ErrorCategory.InvalidArgument, +// this)); +// } +// finally +// { +// if (tempScriptFilePath != null) +// { +// File.Delete(tempScriptFilePath); +// } +// } +// } + +// #endregion +// } +// } diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index 3ba34acf3..ad77a0270 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -5,108 +5,107 @@ Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force Describe "Test New-PSScriptFileInfo" { BeforeAll { - $script:TempPath = Get-TempPath + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDirPaths = @($tmpDir1Path) + Get-NewTestDirs($tmpDirPaths) } BeforeEach { - # Create temp script path - $script:TempScriptPath = Join-Path $script:TempPath "PSGet_$(Get-Random)" - $null = New-Item -Path $script:TempScriptPath -ItemType Directory -Force - - $script:PSScriptInfoName = "PSGetTestScript" - $script:testPSScriptInfoPath = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath "$script:PSScriptInfoName.psd1" + $script:PSScriptInfoName = "test_script" + $script:testScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "$script:PSScriptInfoName.ps1" } AfterEach { - RemoveItem "$script:TempScriptPath" + if (Test-Path -Path $script:testScriptFilePath) + { + Remove-Item $script:testScriptFilePath + } } - ### TODO: Add tests for -Force and -WhatIf if those parameters are applicable -<# It "Create .ps1 file with minimal required fields" { - $Description = "this is a test script" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Test-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath | Should -BeTrue + $description = "Test description" + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Description $description + Test-PSScriptFileInfo -FilePath $script:testScriptFilePath | Should -BeTrue } It "Create .ps1 file with relative path" { - $RelativeCurrentPath = Get-Location - $ScriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "$script:PSScriptInfoName.ps1" - $Description = "this is a test script" - New-PSScriptFileInfo -FilePath $ScriptFilePath -Description $Description + $relativeCurrentPath = Get-Location + $scriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "$script:PSScriptInfoName.ps1" + + $description = "Test description" + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $description - Test-PSScriptFileInfo -FilePath $ScriptFilePath | Should -BeTrue - Remove-Item -Path $ScriptFilePath + Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue + Remove-Item -Path $scriptFilePath } It "Create new .ps1 given Version parameter" { - $Version = "2.0.0.0" - $Description = "Test description" + $version = "2.0.0.0" + $description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Version $Version -Description $Description + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Version $version -Description $description - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Version) | Should -BeTrue - $results.Contains(".VERSION $Version") | Should -BeTrue + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($version) | Should -BeTrue + $results.Contains(".VERSION $version") | Should -BeTrue } It "Create new .ps1 given Guid parameter" { - $Guid = [guid]::NewGuid() - $Description = "Test description" + $guid = [guid]::NewGuid() + $description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Guid $Guid -Description $Description + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Guid $guid -Description $description - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Guid) | Should -BeTrue - $results.Contains(".GUID $Guid") | Should -BeTrue + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($guid) | Should -BeTrue + $results.Contains(".GUID $guid") | Should -BeTrue } It "Create new .ps1 given Author parameter" { - $Author = "Test Author" - $Description = "Test description" + $author = "Test Author" + $description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Author $Author -Description $Description + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Author $author -Description $description - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Author) | Should -BeTrue - $results.Contains(".AUTHOR $Author") | Should -BeTrue + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($author) | Should -BeTrue + $results.Contains(".AUTHOR $author") | Should -BeTrue } It "Create new .ps1 given Description parameter" { - $Description = "PowerShellGet test description" - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($Description) | Should -BeTrue - $results -like ".DESCRIPTION*$Description" | Should -BeTrue + $description = "PowerShellGet test description" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Description $description + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($description) | Should -BeTrue + $results -like "*.DESCRIPTION`n*$description*" | Should -BeTrue } It "Create new .ps1 given CompanyName parameter" { - $CompanyName = "Microsoft" - $Description = "Test description" + $companyName = "Microsoft" + $description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -CompanyName $CompanyName -Description $Description + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -CompanyName $companyName -Description $description - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($CompanyName) | Should -BeTrue - $results.Contains(".COMPANYNAME $Companyname") | Should -BeTrue + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($companyName) | Should -BeTrue + $results.Contains(".COMPANYNAME $companyname") | Should -BeTrue } It "Create new .ps1 given Copyright parameter" { - $Copyright = "(c) Test Corporation" - $Description = "Test description" + $copyright = "(c) Test Corporation" + $description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Copyright $Copyright -Description $Description + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Copyright $copyright -Description $description - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Copyright) | Should -BeTrue - $results.Contains(".COPYRIGHT $Copyright") | Should -BeTrue + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($copyright) | Should -BeTrue + $results.Contains(".COPYRIGHT $copyright") | Should -BeTrue } It "Create new .ps1 given RequiredModules parameter" { @@ -114,186 +113,129 @@ Describe "Test New-PSScriptFileInfo" { $requiredModuleVersion = '1.0.0.0' $RequiredModules = @(@{ModuleName = $requiredModuleName; ModuleVersion = $requiredModuleVersion }) - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredModules $RequiredModules -Description $Description + $description = "Test description" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -RequiredModules $RequiredModules -Description $Description - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw $results.Contains($requiredModuleName) | Should -BeTrue $results.Contains($requiredModuleVersion) | Should -BeTrue - $results -like ".REQUIREDMODULES*$requiredModuleName*$requiredModuleVersion" | Should -BeTrue + $results -like "*#Requires*$requiredModuleName*$requiredModuleVersion*" | Should -BeTrue } It "Create new .ps1 given ReleaseNotes parameter" { - $Description = "Test Description" - $ReleaseNotes = "Release notes for script." + $description = "Test Description" + $releaseNotes = "Release notes for script." - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ReleaseNotes $ReleaseNotes -Description $Description + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -ReleaseNotes $releaseNotes -Description $description - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($ReleaseNotes) | Should -BeTrue - $results -like ".RELEASENOTES*$ReleaseNotes" | Should -BeTrue + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($releaseNotes) | Should -BeTrue + $results -like "*.RELEASENOTES`n*$ReleaseNotes*" | Should -BeTrue } It "Create new .ps1 given Tags parameter" { - $Description = "Test Description" - $Tag1 = "tag1" - $Tag2 = "tag2" + $description = "Test Description" + $tag1 = "tag1" + $tag2 = "tag2" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Tags $Tag1, $Tag2 -Description $Description + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Tags $tag1, $tag2 -Description $description - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($Tag1) | Should -BeTrue - $results.Contains($Tag2) | Should -BeTrue - $results.Contains(".TAGS $Tag1 $Tag2") | Should -BeTrue + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($tag1) | Should -BeTrue + $results.Contains($tag2) | Should -BeTrue + $results.Contains(".TAGS $tag1 $tag2") | Should -BeTrue } It "Create new .ps1 given ProjectUri parameter" { - $Description = "Test Description" - $ProjectUri = "https://www.testprojecturi.com/" - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ProjectUri $ProjectUri -Description $Description - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($ProjectUri) | Should -BeTrue - $results.Contains(".PROJECTURI $ProjectUri") | Should -BeTrue + $description = "Test Description" + $projectUri = "https://www.testprojecturi.com/" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -ProjectUri $projectUri -Description $description + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($projectUri) | Should -BeTrue + $results.Contains(".PROJECTURI $projectUri") | Should -BeTrue } It "Create new .ps1 given LicenseUri parameter" { - $Description = "Test Description" - $LicenseUri = "https://www.testlicenseuri.com/" + $description = "Test Description" + $licenseUri = "https://www.testlicenseuri.com/" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -LicenseUri $LicenseUri -Description $Description + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -LicenseUri $licenseUri -Description $description - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($LicenseUri) | Should -BeTrue - $results.Contains(".LICENSEURI $LicenseUri") | Should -BeTrue + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($licenseUri) | Should -BeTrue + $results.Contains(".LICENSEURI $licenseUri") | Should -BeTrue } It "Create new .ps1 given IconUri parameter" { - $Description = "Test Description" - $IconUri = "https://www.testiconuri.com/" - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -IconUri $IconUri -Description $Description - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($IconUri) | Should -BeTrue - $results.Contains(".ICONURI $IconUri") | Should -BeTrue - } + $description = "Test Description" + $iconUri = "https://www.testiconuri.com/" - It "Create new .ps1 given ExternalModuleDependencies parameter" { - $Description = "Test Description" - $ExternalModuleDep1 = "ExternalModuleDep1" - $ExternalModuleDep2 = "ExternalModuleDep2" - $ExternalModuleDep1FileName = "ExternalModuleDep1.psm1" - $ExternalModuleDep2FileName = "ExternalModuleDep2.psm1" - $ExternalModuleDepPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $ExternalModuleDep1FileName - $ExternalModuleDepPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $ExternalModuleDep2FileName - - $null = New-Item -Path $ExternalModuleDepPath1 -ItemType File -Force - $null = New-Item -Path $ExternalModuleDepPath2 -ItemType File -Force - - # NOTE: you may need to add the -NestedModules parameter here as well - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ExternalModuleDependencies $ExternalModuleDep1, $ExternalModuleDep2 -Description $Description - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($ExternalModuleDep1) | Should -BeTrue - $results.Contains($ExternalModuleDep2) | Should -BeTrue - $results -like ".EXTERNALMODULEDEPENDENCIES*$ExternalModuleDep1*$ExternalModuleDep2" | Should -BeTrue - } + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -IconUri $iconUri -Description $description - It "Create new .ps1 given RequiredAssemblies parameter" { - $Description = "Test Description" - $RequiredAssembly1 = "RequiredAssembly1.dll" - $RequiredAssembly2 = "RequiredAssembly2.dll" - $RequiredAssemblyPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $RequiredAssembly1 - $RequiredAssemblyPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $RequiredAssembly2 + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($iconUri) | Should -BeTrue + $results.Contains(".ICONURI $iconUri") | Should -BeTrue + } - $null = New-Item -Path $RequiredAssemblyPath1 -ItemType File -Force - $null = New-Item -Path $RequiredAssemblyPath2 -ItemType File -Force + It "Create new .ps1 given ExternalModuleDependencies parameter" { + $description = "Test Description" + $externalModuleDep1 = "ExternalModuleDep1" + $externalModuleDep2 = "ExternalModuleDep2" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredAssemblies $RequiredAssembly1, $RequiredAssembly2 -Description $Description + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -ExternalModuleDependencies $externalModuleDep1, $externalModuleDep2 -Description $description - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($RequiredAssembly1) | Should -BeTrue - $results.Contains($RequiredAssembly2) | Should -BeTrue - $results -like ".REQUIREDASSEMBLIES*$RequiredAssembly1*$RequiredAssembly2" | Should -BeTrue - } - - It "Create new .ps1 given NestedModules parameter" { - $Description = "Test Description" - $NestedModule1 = "NestedModule1" - $NestedModule2 = "NestedModule2" - $NestModuleFileName1 = "NestedModule1.dll" - $NestModuleFileName2 = "NestedModule2.dll" - $NestedModulePath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NestModuleFileName1 - $NestedModulePath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NestModuleFileName2 - - $null = New-Item -Path $NestedModulePath1 -ItemType File -Force - $null = New-Item -Path $NestedModulePath2 -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -NestedModules $NestedModule1, $NestedModule2 -Description $Description - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NestedModule1) | Should -BeTrue - $results.Contains($NestedModule2) | Should -BeTrue - $results -like ".NESTEDMODULES*$NestedModule1*$NestedModule2" | Should -BeTrue + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($externalModuleDep1) | Should -BeTrue + $results.Contains($externalModuleDep2) | Should -BeTrue + $results -like "*.EXTERNALMODULEDEPENDENCIES*$externalModuleDep1*$externalModuleDep2*" | Should -BeTrue } It "Create new .ps1 given RequiredScripts parameter" { - $Description = "Test Description" - $RequiredScript1 = "NestedModule1.ps1" - $RequiredScript2 = "NestedModule2.ps1" - $RequiredScript1Path = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $RequiredScript1 - $RequiredScript2Path = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $RequiredScript2 - - $null = New-Item -Path $RequiredScript1Path -ItemType File -Force - $null = New-Item -Path $RequiredScript2Path -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredScripts $RequiredScript1, $RequiredScript2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($RequiredScript1) | Should -BeTrue - $results.Contains($RequiredScript2) | Should -BeTrue - $results -like ".REQUIREDSCRIPTS*$RequiredScript1*$RequiredScript2" | Should -BeTrue + $description = "Test Description" + $requiredScript1 = "RequiredScript1" + $requiredScript2 = "RequiredScript2" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -RequiredScripts $requiredScript1, $requiredScript2 -Description $description + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($requiredScript1) | Should -BeTrue + $results.Contains($requiredScript2) | Should -BeTrue + $results -like "*.REQUIREDSCRIPTS*$requiredScript1*$requiredScript2*" | Should -BeTrue } It "Create new .ps1 given ExternalScriptDependencies parameter" { - $Description = "Test Description" - $ExternalScriptDep1 = "ExternalScriptDep1.ps1" - $ExternalScriptDep2 = "ExternalScriptDep2.ps1" - $ExternalScriptDepPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $ExternalScriptDep1 - $ExternalScriptDepPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $ExternalScriptDep2 - - $null = New-Item -Path $ExternalScriptDepPath1 -ItemType File -Force - $null = New-Item -Path $ExternalScriptDepPath2 -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ExternalModuleDependencies $ExternalModuleDep1, $ExternalModuleDep2 -Description $Description - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($ExternalModuleDep1) | Should -BeTrue - $results.Contains($ExternalModuleDep2) | Should -BeTrue - $results -like ".EXTERNALSCRIPTDEPENDENCIES*$ExternalScriptDep1*$ExternalScriptDep2" | Should -BeTrue + $description = "Test Description" + $externalScriptDep1 = "ExternalScriptDep1" + $externalScriptDep2 = "ExternalScriptDep2" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -ExternalScriptDependencies $externalScriptDep1, $externalScriptDep2 -Description $description + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($externalScriptDep1) | Should -BeTrue + $results.Contains($externalScriptDep2) | Should -BeTrue + $results -like "*.EXTERNALSCRIPTDEPENDENCIES*$externalScriptDep1*$externalScriptDep2*" | Should -BeTrue } It "Create new .ps1 given PrivateData parameter" { - $Description = "Test Description" - $PrivateData = @{"PrivateDataEntry1" = "PrivateDataValue1"} - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -PrivateData $PrivateData -Description $Description - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($PrivateData) | Should -BeTrue - $results -like ".PRIVATEDATA*$PrivateData" | Should -BeTrue + $description = "Test Description" + $privateData = @{"PrivateDataEntry1" = "PrivateDataValue1"} + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -PrivateData $privateData -Description $description + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($privateData) | Should -BeTrue + $results -like "*.PRIVATEDATA*$privateData*" | Should -BeTrue } -#> -} \ No newline at end of file +} diff --git a/test/TestPSScriptFileInfo.Tests.ps1 b/test/TestPSScriptFileInfo.Tests.ps1 new file mode 100644 index 000000000..30adae47b --- /dev/null +++ b/test/TestPSScriptFileInfo.Tests.ps1 @@ -0,0 +1,60 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force + +Describe "Test Test-PSScriptFileInfo" { + BeforeAll { + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDirPaths = @($tmpDir1Path) + Get-NewTestDirs($tmpDirPaths) + + # Path to folder, within our test folder, where we store invalid module and script files used for testing + $script:testFilesFolderPath = Join-Path $psscriptroot -ChildPath "testFiles" + + # Path to specifically to that invalid test scripts folder + $script:testScriptsFolderPath = Join-Path $testFilesFolderPath -ChildPath "testScripts" + } + + It "determine script file with minimal required fields as valid" { + $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testscript.ps1" + $scriptDescription = "this is a test script" + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } + + It "not determine script file with Author field missing as valid" { + $scriptName = "InvalidScriptMissingAuthor.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + Test-PSScriptFileInfo $scriptFilePath | Should -Be $false + } + + It "not determine script file with Description field missing as valid" { + $scriptName = "InvalidScriptMissingDescription.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + Test-PSScriptFileInfo $scriptFilePath | Should -Be $false + } + + It "not determine script that is missing Description block altogether as valid" { + $scriptName = "InvalidScriptMissingDescriptionCommentBlock.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + Test-PSScriptFileInfo $scriptFilePath | Should -Be $false + } + + It "not determine script file Guid as valid" { + $scriptName = "InvalidScriptMissingGuid.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + Test-PSScriptFileInfo $scriptFilePath | Should -Be $false + } + + It "not determine script file missing Version as valid" { + $scriptName = "InvalidScriptMissingVersion.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + Test-PSScriptFileInfo $scriptFilePath | Should -Be $false + } +} diff --git a/test/TestPSScriptInfo.Tests.ps1 b/test/TestPSScriptInfo.Tests.ps1 deleted file mode 100644 index fa79f8bf9..000000000 --- a/test/TestPSScriptInfo.Tests.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force - -Describe "Test Test-PSScriptFileInfo" { - - BeforeAll { - $script:TempPath = Get-TempPath - } - BeforeEach { - # Create temp script path - $script:TempScriptPath = Join-Path $script:TempPath "PSGet_$(Get-Random)" - $null = New-Item -Path $script:TempScriptPath -ItemType Directory -Force - - $script:PSScriptInfoName = "PSGetTestScript" - $script:testPSScriptInfoPath = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath "$script:PSScriptInfoName.psd1" - } - AfterEach { - RemoveItem "$script:TempScriptPath" - } - - ### TODO: Add tests for -Force and -WhatIf if those parameters are applicable - <# - It "Test .ps1 file with minimal required fields" { - $Description = "This is a test script" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Test-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath | Should -BeTrue - } - - It "Test .ps1 file with relative path" { - $RelativeCurrentPath = Get-Location - $ScriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "$script:PSScriptInfoName.ps1" - $Description = "this is a test script" - New-PSScriptFileInfo -FilePath $ScriptFilePath -Description $Description - - Test-PSScriptFileInfo -FilePath $ScriptFilePath | Should -BeTrue - Remove-Item -Path $ScriptFilePath - } - #> -} From 6dc2ab00b02ff10c4d1ff92ba5bc6769b65dbd5c Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 14 Jul 2022 18:53:54 -0400 Subject: [PATCH 06/29] Update-PSScriptFileInfo now works --- src/PowerShellGet.psd1 | 1 + src/code/PSScriptContents.cs | 30 +- src/code/PSScriptFileInfo.cs | 157 ++++- src/code/PSScriptHelp.cs | 30 + src/code/PSScriptMetadata.cs | 93 +++ src/code/PSScriptRequires.cs | 8 + src/code/TestPSScriptFileInfo.cs | 2 +- src/code/UpdatePSScriptFileInfo.cs | 638 +++++++++--------- test/UpdatePSScriptFileInfo.Tests.ps1 | 312 +++++++++ test/UpdatePSScriptInfo.Tests.ps1 | 415 ------------ .../testScripts/ScriptWithSignature.ps1 | 75 ++ 11 files changed, 975 insertions(+), 786 deletions(-) create mode 100644 test/UpdatePSScriptFileInfo.Tests.ps1 delete mode 100644 test/UpdatePSScriptInfo.Tests.ps1 create mode 100644 test/testFiles/testScripts/ScriptWithSignature.ps1 diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index 6500e0e49..2b4953d39 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -23,6 +23,7 @@ 'Set-PSResourceRepository', 'New-PSScriptFileInfo', 'Test-PSScriptFileInfo', + 'Update-PSScriptFileInfo', 'Publish-PSResource', 'Uninstall-PSResource', 'Unregister-PSResourceRepository', diff --git a/src/code/PSScriptContents.cs b/src/code/PSScriptContents.cs index fb001ea31..a55b77596 100644 --- a/src/code/PSScriptContents.cs +++ b/src/code/PSScriptContents.cs @@ -53,9 +53,9 @@ internal PSScriptContents() {} #endregion - #region Public Methods + #region Internal Methods - public void ParseContent(string[] commentLines) + internal void ParseContent(string[] commentLines) { if (commentLines.Length != 0) { @@ -65,23 +65,14 @@ public void ParseContent(string[] commentLines) } /// - /// Checks if end of file contents contains a Signature + /// This function would be called by PSScriptFileInfo.TryCreateScriptFileInfoString(), + /// called by New-PSScriptFileInfo cmdlet (in which case EndOfFileContents is an empty string so there's no signature that'll get removed) + /// or by Update-PSScriptFileInfo cmdlet (in which case EndOfFileContents may not be empty and may contain a signature. + /// and the Update cmdlet checks for -RemoveSignature before control reaches this method) /// - public bool ValidateContent() - { - // if (ContainsSignature) - // { - // // todo: write warning somewhere, change state of ContainsSignature or should it reflect original file state? - // // RemoveSignatureString(); - - // } - - // return true; - return !ContainsSignature; - } - - public string EmitContent() + internal string EmitContent() { + RemoveSignatureString(); return EndOfFileContents; } @@ -92,7 +83,7 @@ public string EmitContent() #region Private Methods /// - /// Checks if the end of file contents contain a signature + /// Checks if the end of file contents contain a signature. /// private bool CheckForSignature() { @@ -109,9 +100,8 @@ private void RemoveSignatureString() { int signatureStartIndex = EndOfFileContents.IndexOf(signatureStartString); EndOfFileContents = EndOfFileContents.Substring(0, signatureStartIndex); + ContainsSignature = false; } - - // TODO: should I set ContainsSignature to false now? } #endregion } diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 8bd7f46cf..629802164 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -95,25 +95,10 @@ PSScriptContents scriptRemainingContent #region Internal Static Methods + /// + /// Tests .ps1 file for validity + /// internal static bool TryParseScriptFile( - string scriptFileInfoPath, - out PSScriptFileInfo parsedScript, - out ErrorRecord[] errors, - out string[] msgs - ) - { - // parse -> create object -> validate - parsedScript = null; - errors = null; - msgs = new string[]{}; - - - - - return true; - } - - internal static bool TryParseScriptFile2( string scriptFileInfoPath, out PSScriptFileInfo parsedScript, out ErrorRecord[] errors, @@ -272,10 +257,132 @@ internal static bool TryParseScriptFile2( PSScriptContents currentEndOfFileContents = new PSScriptContents(); currentEndOfFileContents.ParseContent(commentLines: remainingFileContentArray); + if (!parsedContentSuccessfully) + { + errors = errorsList.ToArray(); + return parsedContentSuccessfully; + } + + try + { + parsedScript = new PSScriptFileInfo( + scriptMetadataComment: currentMetadata, + scriptHelpComment: currentHelpInfo, + scriptRequiresComment: currentRequiresComment, + scriptRemainingContent: currentEndOfFileContents); + } + catch (Exception e) + { + var message = String.Format("PSScriptFileInfo object could not be created from passed in file due to {0}", e.Message); + var ex = new ArgumentException(message); + var PSScriptFileInfoObjectNotCreatedFromFileError = new ErrorRecord(ex, "PSScriptFileInfoObjectNotCreatedFromFileError", ErrorCategory.ParserError, null); + errorsList.Add(PSScriptFileInfoObjectNotCreatedFromFileError); + parsedContentSuccessfully = false; + } + errors = errorsList.ToArray(); return parsedContentSuccessfully; } + /// + /// Updates .ps1 file. + /// Caller must check that the file to update doesn't have a signature or if it does permission to remove signature has been granted + /// as this method will remove original signature, as updating would have invalidated it + /// + internal static bool TryUpdateScriptFileContents( + PSScriptFileInfo scriptInfo, + out string updatedPSScriptFileContents, + out ErrorRecord[] errors, + string version, + Guid guid, + string author, + string companyName, + string copyright, + string[] tags, + Uri licenseUri, + Uri projectUri, + Uri iconUri, + ModuleSpecification[] requiredModules, + string[] externalModuleDependencies, + string[] requiredScripts, + string[] externalScriptDependencies, + string releaseNotes, + string privateData, + string description) + { + updatedPSScriptFileContents = String.Empty; + List errorsList = new List(); + bool successfullyUpdated = true; + + if (scriptInfo == null) + { + var message = String.Format("Could not update .ps1 file as PSScriptFileInfo object created for it was null"); + var ex = new ArgumentException(message); + var nullPSScriptFileInfoError = new ErrorRecord(ex, "NullPSScriptFileInfoError", ErrorCategory.ParserError, null); + errors = new ErrorRecord[]{nullPSScriptFileInfoError}; + + return false; + } + + if (!scriptInfo.ScriptMetadataCommment.UpdateContent( + version: version, + guid: guid, + author: author, + companyName: companyName, + copyright: copyright, + tags: tags, + licenseUri: licenseUri, + projectUri: projectUri, + iconUri: iconUri, + externalModuleDependencies: externalModuleDependencies, + requiredScripts: requiredScripts, + externalScriptDependencies: externalScriptDependencies, + releaseNotes: releaseNotes, + privateData: privateData, + out ErrorRecord metadataUpdateError)) + { + errorsList.Add(metadataUpdateError); + successfullyUpdated = false; + } + + if (!scriptInfo.ScriptHelpComment.UpdateContent( + description: description, + out ErrorRecord helpUpdateError)) // todo: check v2 if other things can be updated? like Example? + { + errorsList.Add(helpUpdateError); + successfullyUpdated = false; + } + + // this doesn't produce errors, as ModuleSpecification creation is already validated before param passed in + // and user can't update endOfFileContents + scriptInfo.ScriptRequiresComment.UpdateContent(requiredModules: requiredModules); + + if (!successfullyUpdated) + { + errors = errorsList.ToArray(); + return successfullyUpdated; + } + + // create string contents for .ps1 file + if (!scriptInfo.TryCreateScriptFileInfoString( + psScriptFileString: out updatedPSScriptFileContents, + errors: out ErrorRecord[] createUpdatedFileContentErrors)) + { + errorsList.AddRange(createUpdatedFileContentErrors); + successfullyUpdated = false; + } + + errors = errorsList.ToArray(); + return successfullyUpdated; + } + + #endregion + + #region Internal Methods + + /// + /// Creates .ps1 file content string representation for the PSScriptFileInfo object this called upon, which is used by the caller to write the .ps1 file. + /// internal bool TryCreateScriptFileInfoString( out string psScriptFileString, out ErrorRecord[] errors @@ -287,7 +394,7 @@ out ErrorRecord[] errors bool fileContentsSuccessfullyCreated = true; - // step 1: validate + // step 1: validate object properties for required script properties if (!ScriptMetadataCommment.ValidateContent(out ErrorRecord[] metadataValidationErrors)) { errorsList.AddRange(metadataValidationErrors); @@ -329,18 +436,6 @@ out ErrorRecord[] errors return fileContentsSuccessfullyCreated; } - - internal static bool TryUpdateScriptFileContents( - PSScriptFileInfo scriptInfo, - out string updatedPSScriptFileContents, - out ErrorRecord[] errors - ) - { - updatedPSScriptFileContents = string.Empty; - errors = null; - return true; - } - #endregion } } diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs index fb4cfc4b5..d8a1a4dfd 100644 --- a/src/code/PSScriptHelp.cs +++ b/src/code/PSScriptHelp.cs @@ -299,6 +299,36 @@ internal string EmitContent() return psHelpInfo; } + internal bool UpdateContent(string description, out ErrorRecord error) + { + error = null; + + if (!String.IsNullOrEmpty(description)) + { + if (String.Equals(description.Trim(), String.Empty)) + { + var exMessage = "Description value can't be updated to whitespace as this would invalidate the script."; + var ex = new ArgumentException(exMessage); + var descriptionUpdateValueIsWhitespaceError = new ErrorRecord(ex, "descriptionUpdateValueIsWhitespaceError", ErrorCategory.InvalidArgument, null); + error = descriptionUpdateValueIsWhitespaceError; + return false; + } + + if (StringContainsComment(description)) + { + var exMessage = "Description value can't be updated to value containing comment '<#' or '#>' as this would invalidate the script."; + var ex = new ArgumentException(exMessage); + var descriptionUpdateValueContainsCommentError = new ErrorRecord(ex, "descriptionUpdateValueContainsCommentError", ErrorCategory.InvalidArgument, null); + error = descriptionUpdateValueContainsCommentError; + return false; + } + + Description = description; + } + + return true; + } + #endregion #region Private Methods diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs index 1339cc6d9..49e4655b8 100644 --- a/src/code/PSScriptMetadata.cs +++ b/src/code/PSScriptMetadata.cs @@ -435,6 +435,99 @@ Feature 5 return String.Join("\n\n", psScriptInfoLines); } + + internal bool UpdateContent( + string version, + Guid guid, + string author, + string companyName, + string copyright, + string[] tags, + Uri licenseUri, + Uri projectUri, + Uri iconUri, + string[] externalModuleDependencies, + string[] requiredScripts, + string[] externalScriptDependencies, + string releaseNotes, + string privateData, + out ErrorRecord error) + { + error = null; + if (!String.IsNullOrEmpty(version)) + { + if (!NuGetVersion.TryParse(version, out NuGetVersion updatedVersion)) + { + var message = String.Format("Version provided for update could not be parsed successfully into NuGetVersion"); + var ex = new ArgumentException(message); + var versionParseIntoNuGetVersionError = new ErrorRecord(ex, "VersionParseIntoNuGetVersion", ErrorCategory.ParserError, null); + error = versionParseIntoNuGetVersionError; + return false; + } + + Version = updatedVersion; + } + + if (guid != Guid.Empty) + { + Guid = guid; + } + + if (!String.IsNullOrEmpty(author)) + { + Author = author; + } + + if (!String.IsNullOrEmpty(companyName)){ + CompanyName = companyName; + } + + if (!String.IsNullOrEmpty(copyright)){ + Copyright = copyright; + } + + if (tags != null && tags.Length != 0){ + Tags = tags; + } + + if (licenseUri != null && !licenseUri.Equals(default(Uri))){ + LicenseUri = licenseUri; + } + + if (projectUri != null && !projectUri.Equals(default(Uri))){ + ProjectUri = projectUri; + } + + if (iconUri != null && !iconUri.Equals(default(Uri))){ + IconUri = iconUri; + } + + if (externalModuleDependencies != null && externalModuleDependencies.Length != 0){ + ExternalModuleDependencies = externalModuleDependencies; + } + + if (requiredScripts != null && requiredScripts.Length != 0) + { + RequiredScripts = requiredScripts; + } + + if (externalScriptDependencies != null && externalScriptDependencies.Length != 0){ + ExternalScriptDependencies = externalScriptDependencies; + } + + if (!String.IsNullOrEmpty(releaseNotes)) + { + ReleaseNotes = releaseNotes; + } + + if (!String.IsNullOrEmpty(privateData)) + { + PrivateData = privateData; + } + + return true; + } + #endregion } } \ No newline at end of file diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs index 9becfdff8..352eac7ec 100644 --- a/src/code/PSScriptRequires.cs +++ b/src/code/PSScriptRequires.cs @@ -125,6 +125,14 @@ internal string EmitContent() return String.Empty; } + internal void UpdateContent(ModuleSpecification[] requiredModules) + { + if (requiredModules != null && requiredModules.Length != 0){ + RequiredModules = requiredModules; + } + } + + #endregion } } \ No newline at end of file diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 997508531..5922bf0ba 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -58,7 +58,7 @@ protected override void EndProcessing() ThrowTerminatingError(FileDoesNotExistError); } - bool isValidScript = PSScriptFileInfo.TryParseScriptFile2( + bool isValidScript = PSScriptFileInfo.TryParseScriptFile( scriptFileInfoPath: resolvedFilePath, parsedScript: out PSScriptFileInfo parsedScriptInfo, errors: out ErrorRecord[] errors, diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 3408ee49b..f49dcd6c4 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -1,322 +1,322 @@ -// // Copyright (c) Microsoft Corporation. All rights reserved. -// // Licensed under the MIT License. - -// using System; -// using System.Collections; -// using System.IO; -// using System.Management.Automation; -// using Microsoft.PowerShell.Commands; -// using Microsoft.PowerShell.PowerShellGet.UtilClasses; - -// namespace Microsoft.PowerShell.PowerShellGet.Cmdlets -// { -// /// -// /// Updates a .ps1 file with specified properties. -// /// -// [Cmdlet(VerbsData.Update, "PSScriptFileInfo")] -// public sealed class UpdatePSScriptFileInfo : PSCmdlet -// { -// #region Parameters - -// /// -// /// The author of the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string Author { get; set; } - -// /// -// /// The name of the company owning the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string CompanyName { get; set; } - -// /// -// /// The copyright statement for the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string Copyright { get; set; } - -// /// -// /// The description of the script. -// /// -// [Parameter()] -// [ValidateNotNullOrEmpty()] -// public string Description { get; set; } - -// /// -// /// The list of external module dependencies taken by this script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string[] ExternalModuleDependencies { get; set; } - -// /// -// /// The list of external script dependencies taken by this script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string[] ExternalScriptDependencies { get; set; } - -// /// -// /// The unique identifier for the script. The GUID can be used to distinguish among scripts with the same name. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public Guid Guid { get; set; } - -// /// -// /// The Uri for the icon associated with the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string IconUri { get; set; } - -// /// -// /// The Uri for the license associated with the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string LicenseUri { get; set; } - -// /// -// /// The path the .ps1 script info file will be created at. -// /// -// [Parameter(Position = 0, Mandatory = true)] -// [ValidateNotNullOrEmpty] -// public string FilePath { get; set; } - -// /// -// /// The private data associated with the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string PrivateData { get; set; } - -// /// -// /// The Uri for the project associated with the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string ProjectUri { get; set; } - -// /// -// /// The release notes for the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string ReleaseNotes { get; set; } - -// /// -// /// Remove signature from signed .ps1 (if present) thereby allowing update of script to happen -// /// User should re-sign the updated script afterwards. -// /// -// [Parameter] -// public SwitchParameter RemoveSignature { get; set; } - -// /// -// /// The list of modules required by the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public Hashtable[] RequiredModules { get; set; } - -// /// -// /// The list of scripts required by the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string[] RequiredScripts { get; set; } - -// /// -// /// The tags associated with the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string[] Tags { get; set; } - -// /// -// /// The version of the script. -// /// -// [Parameter] -// [ValidateNotNullOrEmpty()] -// public string Version { get; set; } - -// #endregion - -// #region Private Members - -// private const string signatureStartString = "# SIG # Begin signature block"; - -// #endregion - -// #region Methods - -// protected override void EndProcessing() -// { -// Uri projectUri = null; -// if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri, -// cmdletPassedIn: this, -// uriResult: out projectUri, -// errorRecord: out ErrorRecord projectErrorRecord)) -// { -// ThrowTerminatingError(projectErrorRecord); -// } - -// Uri licenseUri = null; -// if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUri(uriString: LicenseUri, -// cmdletPassedIn: this, -// uriResult: out licenseUri, -// errorRecord: out ErrorRecord licenseErrorRecord)) -// { -// ThrowTerminatingError(licenseErrorRecord); -// } - -// Uri iconUri = null; -// if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUri(uriString: IconUri, -// cmdletPassedIn: this, -// uriResult: out iconUri, -// errorRecord: out ErrorRecord iconErrorRecord)) -// { -// ThrowTerminatingError(iconErrorRecord); -// } - -// if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) -// { -// var exMessage = "File path needs to end with a .ps1 extension. Example: C:/Users/john/x/MyScript.ps1"; -// var ex = new ArgumentException(exMessage); -// var InvalidOrNonExistantPathError = new ErrorRecord(ex, "InvalidOrNonExistantPath", ErrorCategory.InvalidArgument, null); -// ThrowTerminatingError(InvalidOrNonExistantPathError); -// } - -// var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); -// if (resolvedPaths.Count != 1) -// { -// var exMessage = "Error: Could not resolve provided Path argument into a single path."; -// var ex = new PSArgumentException(exMessage); -// var InvalidPathArgumentError = new ErrorRecord(ex, "InvalidPathArgumentError", ErrorCategory.InvalidArgument, null); -// ThrowTerminatingError(InvalidPathArgumentError); -// } - -// string resolvedFilePath = resolvedPaths[0].Path; - -// if (!File.Exists(resolvedFilePath)) -// { -// var exMessage = "A script file does not exist at the location specified"; -// var ex = new ArgumentException(exMessage); -// var FileDoesNotExistError = new ErrorRecord(ex, "FileDoesNotExistAtPath", ErrorCategory.InvalidArgument, null); -// ThrowTerminatingError(FileDoesNotExistError); -// } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.IO; +using System.Management.Automation; +using Microsoft.PowerShell.Commands; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// Updates a .ps1 file with specified properties. + /// + [Cmdlet(VerbsData.Update, "PSScriptFileInfo")] + public sealed class UpdatePSScriptFileInfo : PSCmdlet + { + #region Parameters + + /// + /// The author of the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Author { get; set; } + + /// + /// The name of the company owning the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string CompanyName { get; set; } + + /// + /// The copyright statement for the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Copyright { get; set; } + + /// + /// The description of the script. + /// + [Parameter()] + [ValidateNotNullOrEmpty()] + public string Description { get; set; } + + /// + /// The list of external module dependencies taken by this script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] ExternalModuleDependencies { get; set; } + + /// + /// The list of external script dependencies taken by this script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] ExternalScriptDependencies { get; set; } + + /// + /// The unique identifier for the script. The GUID can be used to distinguish among scripts with the same name. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public Guid Guid { get; set; } + + /// + /// The Uri for the icon associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string IconUri { get; set; } + + /// + /// The Uri for the license associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string LicenseUri { get; set; } + + /// + /// The path the .ps1 script info file will be created at. + /// + [Parameter(Position = 0, Mandatory = true)] + [ValidateNotNullOrEmpty] + public string FilePath { get; set; } + + /// + /// The private data associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string PrivateData { get; set; } + + /// + /// The Uri for the project associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string ProjectUri { get; set; } + + /// + /// The release notes for the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string ReleaseNotes { get; set; } + + /// + /// Remove signature from signed .ps1 (if present) thereby allowing update of script to happen + /// User should re-sign the updated script afterwards. + /// + [Parameter] + public SwitchParameter RemoveSignature { get; set; } + + /// + /// The list of modules required by the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public Hashtable[] RequiredModules { get; set; } + + /// + /// The list of scripts required by the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] RequiredScripts { get; set; } + + /// + /// The tags associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] Tags { get; set; } + + /// + /// The version of the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Version { get; set; } + + #endregion + + #region Private Members + + private const string signatureStartString = "# SIG # Begin signature block"; + + #endregion + + #region Methods + + protected override void EndProcessing() + { + Uri projectUri = null; + if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri, + cmdletPassedIn: this, + uriResult: out projectUri, + errorRecord: out ErrorRecord projectErrorRecord)) + { + ThrowTerminatingError(projectErrorRecord); + } + + Uri licenseUri = null; + if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUri(uriString: LicenseUri, + cmdletPassedIn: this, + uriResult: out licenseUri, + errorRecord: out ErrorRecord licenseErrorRecord)) + { + ThrowTerminatingError(licenseErrorRecord); + } + + Uri iconUri = null; + if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUri(uriString: IconUri, + cmdletPassedIn: this, + uriResult: out iconUri, + errorRecord: out ErrorRecord iconErrorRecord)) + { + ThrowTerminatingError(iconErrorRecord); + } + + if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + { + var exMessage = "File path needs to end with a .ps1 extension. Example: C:/Users/john/x/MyScript.ps1"; + var ex = new ArgumentException(exMessage); + var InvalidOrNonExistantPathError = new ErrorRecord(ex, "InvalidOrNonExistantPath", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InvalidOrNonExistantPathError); + } + + var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); + if (resolvedPaths.Count != 1) + { + var exMessage = "Error: Could not resolve provided Path argument into a single path."; + var ex = new PSArgumentException(exMessage); + var InvalidPathArgumentError = new ErrorRecord(ex, "InvalidPathArgumentError", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InvalidPathArgumentError); + } + + string resolvedFilePath = resolvedPaths[0].Path; + + if (!File.Exists(resolvedFilePath)) + { + var exMessage = "A script file does not exist at the location specified"; + var ex = new ArgumentException(exMessage); + var FileDoesNotExistError = new ErrorRecord(ex, "FileDoesNotExistAtPath", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(FileDoesNotExistError); + } -// ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; -// if (RequiredModules != null && RequiredModules.Length > 0) -// { -// if (!Utils.TryCreateModuleSpecification( -// moduleSpecHashtables: RequiredModules, -// out validatedRequiredModuleSpecifications, -// out ErrorRecord[] moduleSpecErrors)) -// { -// foreach (ErrorRecord err in moduleSpecErrors) -// { -// WriteError(err); -// } - -// return; -// } -// } - -// if (!PSScriptFileInfo.TryParseScriptIntoPSScriptInfo( -// scriptFileInfoPath: resolvedFilePath, -// parsedScript: out PSScriptFileInfo parsedScriptInfo, -// errors: out ErrorRecord[] errors, -// out string[] verboseMsgs)) -// { -// foreach (string msg in verboseMsgs) -// { -// WriteVerbose(msg); - -// // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. -// WriteWarning(msg); -// } - -// WriteWarning("The .ps1 script file passed in was not valid due to the following error(s) listed below"); -// foreach (ErrorRecord error in errors) -// { -// WriteError(error); -// } - -// return; -// } - -// if (parsedScriptInfo.EndOfFileContents.Contains(signatureStartString)) -// { -// WriteWarning("This script contains a signature and cannot be updated without invalidating the current script signature"); -// if (!RemoveSignature) -// { -// var exMessage = "Cannot update the script file because the file contains a signature block and updating will invalidate the signature. Use -RemoveSignature to remove the signature block, and then re-sign the file after it is updated."; -// var ex = new PSInvalidOperationException(exMessage); -// var ScriptToBeUpdatedContainsSignatureError = new ErrorRecord(ex, "ScriptToBeUpdatedContainsSignature", ErrorCategory.InvalidOperation, null); -// ThrowTerminatingError(ScriptToBeUpdatedContainsSignatureError); -// } -// } + ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; + if (RequiredModules != null && RequiredModules.Length > 0) + { + if (!Utils.TryCreateModuleSpecification( + moduleSpecHashtables: RequiredModules, + out validatedRequiredModuleSpecifications, + out ErrorRecord[] moduleSpecErrors)) + { + foreach (ErrorRecord err in moduleSpecErrors) + { + WriteError(err); + } + + return; + } + } + + if (!PSScriptFileInfo.TryParseScriptFile( + scriptFileInfoPath: resolvedFilePath, + parsedScript: out PSScriptFileInfo parsedScriptInfo, + errors: out ErrorRecord[] errors, + out string[] verboseMsgs)) + { + foreach (string msg in verboseMsgs) + { + WriteVerbose(msg); + + // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. + WriteWarning(msg); + } + + WriteWarning("The .ps1 script file passed in was not valid due to the following error(s) listed below"); + foreach (ErrorRecord error in errors) + { + WriteError(error); + } + + return; + } + + if (parsedScriptInfo.ScriptContent.ContainsSignature) + { + WriteWarning("This script contains a signature and cannot be updated without invalidating the current script signature"); + if (!RemoveSignature) + { + var exMessage = "Cannot update the script file because the file contains a signature block and updating will invalidate the signature. Use -RemoveSignature to remove the signature block, and then re-sign the file after it is updated."; + var ex = new PSInvalidOperationException(exMessage); + var ScriptToBeUpdatedContainsSignatureError = new ErrorRecord(ex, "ScriptToBeUpdatedContainsSignature", ErrorCategory.InvalidOperation, null); + ThrowTerminatingError(ScriptToBeUpdatedContainsSignatureError); + } + } -// if (!PSScriptFileInfo.TryUpdateScriptFileContents( -// scriptInfo: parsedScriptInfo, -// updatedPSScriptFileContents: out string updatedPSScriptFileContents, -// errors: out ErrorRecord[] updateErrors, -// version: Version, -// guid: Guid, -// author: Author, -// companyName: CompanyName, -// copyright: Copyright, -// tags: Tags, -// licenseUri: licenseUri, -// projectUri: projectUri, -// iconUri: iconUri, -// requiredModules: validatedRequiredModuleSpecifications, -// externalModuleDependencies: ExternalModuleDependencies, -// requiredScripts: RequiredScripts, -// externalScriptDependencies: ExternalScriptDependencies, -// releaseNotes: ReleaseNotes, -// privateData: PrivateData, -// description: Description)) -// { -// WriteWarning("Updating the specified script file failed due to the following error(s):"); -// foreach (ErrorRecord error in updateErrors) -// { -// WriteError(error); -// } - -// return; -// } + if (!PSScriptFileInfo.TryUpdateScriptFileContents( + scriptInfo: parsedScriptInfo, + updatedPSScriptFileContents: out string updatedPSScriptFileContents, + errors: out ErrorRecord[] updateErrors, + version: Version, + guid: Guid, + author: Author, + companyName: CompanyName, + copyright: Copyright, + tags: Tags, + licenseUri: licenseUri, + projectUri: projectUri, + iconUri: iconUri, + requiredModules: validatedRequiredModuleSpecifications, + externalModuleDependencies: ExternalModuleDependencies, + requiredScripts: RequiredScripts, + externalScriptDependencies: ExternalScriptDependencies, + releaseNotes: ReleaseNotes, + privateData: PrivateData, + description: Description)) + { + WriteWarning("Updating the specified script file failed due to the following error(s):"); + foreach (ErrorRecord error in updateErrors) + { + WriteError(error); + } + + return; + } -// string tempScriptFilePath = null; -// try -// { -// tempScriptFilePath = Path.GetTempFileName(); - -// File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); -// File.Copy(tempScriptFilePath, resolvedFilePath, overwrite: true); -// } -// catch(Exception e) -// { -// WriteError(new ErrorRecord( -// new PSInvalidOperationException($"Could not update .ps1 file due to: {e.Message}"), -// "FileIOErrorDuringUpdate", -// ErrorCategory.InvalidArgument, -// this)); -// } -// finally -// { -// if (tempScriptFilePath != null) -// { -// File.Delete(tempScriptFilePath); -// } -// } -// } - -// #endregion -// } -// } + string tempScriptFilePath = null; + try + { + tempScriptFilePath = Path.GetTempFileName(); + + File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); + File.Copy(tempScriptFilePath, resolvedFilePath, overwrite: true); + } + catch(Exception e) + { + WriteError(new ErrorRecord( + new PSInvalidOperationException($"Could not update .ps1 file due to: {e.Message}"), + "FileIOErrorDuringUpdate", + ErrorCategory.InvalidArgument, + this)); + } + finally + { + if (tempScriptFilePath != null) + { + File.Delete(tempScriptFilePath); + } + } + } + + #endregion + } +} diff --git a/test/UpdatePSScriptFileInfo.Tests.ps1 b/test/UpdatePSScriptFileInfo.Tests.ps1 new file mode 100644 index 000000000..91d59bbcd --- /dev/null +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -0,0 +1,312 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force + +Describe "Test Update-PSScriptFileInfo" { + BeforeAll { + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDirPaths = @($tmpDir1Path) + Get-NewTestDirs($tmpDirPaths) + + # Path to folder, within our test folder, where we store invalid module and script files used for testing + $script:testFilesFolderPath = Join-Path $psscriptroot -ChildPath "testFiles" + + # Path to specifically to that invalid test scripts folder + $script:testScriptsFolderPath = Join-Path $testFilesFolderPath -ChildPath "testScripts" + } + + BeforeEach { + $script:psScriptInfoName = "test_script" + $scriptDescription = "this is a test script" + $script:testScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "$script:psScriptInfoName.ps1" + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Description $scriptDescription + } + + AfterEach { + if (Test-Path -Path $script:testScriptFilePath) + { + Remove-Item $script:testScriptFilePath + } + } + + It "Update .ps1 file with relative path" { + $relativeCurrentPath = Get-Location + $scriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "$script:psScriptInfoName.ps1" + $oldDescription = "Old description for test script" + $newDescription = "New description for test script" + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $oldDescription + + Update-PSScriptFileInfo -FilePath $scriptFilePath -Description $newDescription + Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue + + Test-Path -Path $scriptFilePath | Should -BeTrue + $results = Get-Content -Path $scriptFilePath -Raw + $results.Contains($newDescription) | Should -BeTrue + $results -like "*.DESCRIPTION`n*$newDescription*" | Should -BeTrue + + Remove-Item -Path $scriptFilePath -Force + } + + It "Update script should not overwrite old script data unless that property is specified" { + $description = "Test Description" + $version = "3.0.0" + $author = "John Doe" + $newAuthor = "Jane Doe" + $projectUri = "https://testscript.com/" + + $relativeCurrentPath = Get-Location + $scriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "$script:psScriptInfoName.ps1" + + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $description -Version $version -Author $author -ProjectUri $projectUri + Update-PSScriptFileInfo -FilePath $scriptFilePath -Author $newAuthor + + Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue + $results = Get-Content -Path $scriptFilePath -Raw + $results.Contains($newAuthor) | Should -BeTrue + $results.Contains(".AUTHOR $newAuthor") | Should -BeTrue + + # rest should be original data used when creating the script + $results.Contains($projectUri) | Should -BeTrue + $results.Contains(".PROJECTURI $projectUri") | Should -BeTrue + + $results.Contains($version) | Should -BeTrue + $results.Contains(".VERSION $version") | Should -BeTrue + + $results.Contains($description) | Should -BeTrue + $results -like "*.DESCRIPTION`n*$description*" | Should -BeTrue + + Remove-Item -Path $scriptFilePath -Force + } + + It "update script file Author property" { + $author = "New Author" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Author $author + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($author) | Should -BeTrue + $results.Contains(".AUTHOR $author") | Should -BeTrue + } + + It "update script file Version property" { + $version = "2.0.0.0" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Version $version + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($version) | Should -BeTrue + $results.Contains(".VERSION $version") | Should -BeTrue + } + + It "update script file Version property with prerelease version" { + $version = "3.0.0-alpha" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Version $version + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($version) | Should -BeTrue + $results.Contains(".VERSION $version") | Should -BeTrue + } + + It "not update script file with invalid version" { + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Version "4.0.0.0.0" -ErrorVariable err -ErrorAction SilentlyContinue + $err.Count | Should -Not -Be 0 + $err[0].FullyQualifiedErrorId | Should -BeExactly "VersionParseIntoNuGetVersion,Microsoft.PowerShell.PowerShellGet.Cmdlets.UpdatePSScriptFileInfo" + } + + It "update script file Description property" { + $description = "this is an updated test script" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Description $description + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($description) | Should -BeTrue + $results -like "*.DESCRIPTION`n*$description*" | Should -BeTrue + } + + It "update script file Guid property" { + $guid = [Guid]::NewGuid(); + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Guid $guid + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($guid) | Should -BeTrue + $results.Contains(".GUID $guid") | Should -BeTrue + } + + It "update script file CompanyName property" { + $companyName = "New Corporation" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -CompanyName $companyName + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($companyName) | Should -BeTrue + $results.Contains(".COMPANYNAME $companyName") | Should -BeTrue + } + + It "update script file Copyright property" { + $copyright = "(c) 2022 New Corporation. All rights reserved" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Copyright $copyright + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($copyright) | Should -BeTrue + $results.Contains(".COPYRIGHT $copyright") | Should -BeTrue + } + + It "update script file ExternalModuleDependencies property" { + $externalModuleDep1 = "ExternalModuleDep1" + $externalModuleDep2 = "ExternalModuleDep2" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -ExternalModuleDependencies $externalModuleDep1,$externalModuleDep2 + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($externalModuleDep1) | Should -BeTrue + $results.Contains($externalModuleDep2) | Should -BeTrue + $results -like "*.EXTERNALMODULEDEPENDENCIES*$externalModuleDep1*$externalModuleDep2*" | Should -BeTrue + } + + It "update script file ExternalScriptDependencies property" { + $externalScriptDep1 = "ExternalScriptDep1" + $externalScriptDep2 = "ExternalScriptDep2" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -ExternalScriptDependencies $externalScriptDep1,$externalScriptDep2 + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($externalScriptDep1) | Should -BeTrue + $results.Contains($externalScriptDep2) | Should -BeTrue + $results -like "*.EXTERNALMODULEDEPENDENCIES*$externalScriptDep1*$externalScriptDep2*" | Should -BeTrue + } + + It "update script file IconUri property" { + $iconUri = "https://testscript.com/icon" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -IconUri $iconUri + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($iconUri) | Should -BeTrue + $results.Contains(".ICONURI $iconUri") | Should -BeTrue + } + + It "update script file LicenseUri property" { + $licenseUri = "https://testscript.com/license" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -LicenseUri $licenseUri + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($licenseUri) | Should -BeTrue + $results.Contains(".LICENSEURI $licenseUri") | Should -BeTrue + } + + It "update script file ProjectUri property" { + $projectUri = "https://testscript.com/" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -ProjectUri $projectUri + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($projectUri) | Should -BeTrue + $results.Contains(".PROJECTURI $projectUri") | Should -BeTrue + } + + It "update script file PrivateData property" { + $privateData = "this is some private data" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -PrivateData $privateData + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($privateData) | Should -BeTrue + $results -like "*.PRIVATEDATA*$privateData*" | Should -BeTrue + } + + It "update script file ReleaseNotes property" { + $releaseNotes = "Release notes for script." + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -ReleaseNotes $releaseNotes + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($releaseNotes) | Should -BeTrue + $results -like "*.RELEASENOTES`n*$releaseNotes*" | Should -BeTrue + } + + It "update script file RequiredModules property" { + $hashtable1 = @{ModuleName = "RequiredModule1"} + $hashtable2 = @{ModuleName = "RequiredModule2"; ModuleVersion = "1.0.0.0"} + $hashtable3 = @{ModuleName = "RequiredModule3"; RequiredVersion = "2.5.0.0"} + $hashtable4 = @{ModuleName = "RequiredModule4"; ModuleVersion = "1.1.0.0"; MaximumVersion = "2.0.0.0"} + $requiredModules = $hashtable1, $hashtable2, $hashtable3, $hashtable4 + + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -RequiredModules $requiredModules + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains("#Requires -Module RequiredModule1") | Should -BeTrue + $results -like "*#Requires*ModuleName*Version*" | Should -BeTrue + } + + It "update script file RequiredScripts property" { + $requiredScript1 = "RequiredScript1" + $requiredScript2 = "RequiredScript2" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -RequiredScripts $requiredScript1, $requiredScript2 + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($requiredScript1) | Should -BeTrue + $results.Contains($requiredScript2) | Should -BeTrue + $results -like "*.REQUIREDSCRIPTS*$requiredScript1*$requiredScript2*" | Should -BeTrue + } + + It "update script file Tags property" { + $tag1 = "tag1" + $tag2 = "tag2" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Tags $tag1, $tag2 + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($tag1) | Should -BeTrue + $results.Contains($tag2) | Should -BeTrue + $results.Contains(".TAGS $tag1 $tag2") | Should -BeTrue + } + + It "throw error when attempting to update a signed script without -RemoveSignature parameter" { + # Note: user should sign the script again once it's been updated + + $scriptName = "ScriptWithSignature.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + # use a copy of the signed script file so we can re-use for other tests + $null = Copy-Item -Path $scriptFilePath -Destination $TestDrive + $tmpScriptFilePath = Join-Path -Path $TestDrive -ChildPath $scriptName + + { Update-PSScriptFileInfo -FilePath $tmpScriptFilePath -Version "2.0.0.0" } | Should -Throw -ErrorId "ScriptToBeUpdatedContainsSignature,Microsoft.PowerShell.PowerShellGet.Cmdlets.UpdatePSScriptFileInfo" + } + + It "update signed script when using RemoveSignature parameter" { + $scriptName = "ScriptWithSignature.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + # use a copy of the signed script file so we can re-use for other tests + $null = Copy-Item -Path $scriptFilePath -Destination $TestDrive + $tmpScriptFilePath = Join-Path -Path $TestDrive -ChildPath $scriptName + + Update-PSScriptFileInfo -FilePath $tmpScriptFilePath -Version "2.0.0.0" -RemoveSignature + Test-PSScriptFileInfo -FilePath $tmpScriptFilePath | Should -Be $true + } +} diff --git a/test/UpdatePSScriptInfo.Tests.ps1 b/test/UpdatePSScriptInfo.Tests.ps1 deleted file mode 100644 index eb1450eaf..000000000 --- a/test/UpdatePSScriptInfo.Tests.ps1 +++ /dev/null @@ -1,415 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force - -Describe "Test Update-PSScriptFileInfo" { - - BeforeAll { - $script:TempPath = Get-TempPath - } - BeforeEach { - # Create temp script path - $script:TempScriptPath = Join-Path $script:TempPath "PSGet_$(Get-Random)" - $null = New-Item -Path $script:TempScriptPath -ItemType Directory -Force - - $script:PSScriptInfoName = "PSGetTestScript" - $script:testPSScriptInfoPath = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath "$script:PSScriptInfoName.psd1" - } - AfterEach { - RemoveItem "$script:TempScriptPath" - } - - ### TODO: Add tests for -Force and -WhatIf if those parameters are applicable -<# - It "Update .ps1 file with relative path" { - $RelativeCurrentPath = Get-Location - $ScriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "$script:PSScriptInfoName.ps1" - $OldDescription = "Old description for test script" - $NewDescription = "Old description for test script" - New-PSScriptFileInfo -FilePath $ScriptFilePath -Description $OldDescription - - Update-PSScriptFileInfo -FilePath $ScriptFilePath -Description $NewDescription - - Test-PSScriptFileInfo -FilePath $ScriptFilePath | Should -BeTrue - Remove-Item -Path $ScriptFilePath - } - - It "Update .ps1 given Version parameter" { - $Version = "2.0.0.0" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Version $Version - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Version) | Should -BeTrue - $results.Contains(".VERSION $Version") | Should -BeTrue - } - - It "Update .ps1 given prerelease version" { - $Version = "2.0.0.0-alpha" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Version $Version - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Version) | Should -BeTrue - $results.Contains(".VERSION $Version") | Should -BeTrue - } - - It "Should not update .ps1 with invalid version" { - $Version = "4.0.0.0.0" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Version $Version -ErrorVariable err -ErrorAction SilentlyContinue - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeFalse - $err.Count | Should -Not -Be 0 - $err[0].FullyQualifiedErrorId | Should -BeExactly "VersionParseIntoNuGetVersion,Microsoft.PowerShell.PowerShellGet.Cmdlets.UpdatePSScriptFileInfo" - } - - It "Update .ps1 given Guid parameter" { - $Guid = [guid]::NewGuid() - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Guid $Guid - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Guid) | Should -BeTrue - $results.Contains(".GUID $Guid") | Should -BeTrue - } - - It "Update .ps1 given Author parameter" { - $Author = "New Author" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Author $Author - - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Author) | Should -BeTrue - $results.Contains(".AUTHOR $Author") | Should -BeTrue - } - - It "Update .ps1 given Description parameter" { - $OldDescription = "Old description for test script." - $NewDescription = "New description for test script." - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $OldDescription - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $NewDescription - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewDescription) | Should -BeTrue - $results -like ".DESCRIPTION*$NewDescription" | Should -BeTrue - } - - It "Update .ps1 given CompanyName parameter" { - $OldCompanyName = "Old company name" - $NewCompanyName = "New company name" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -CompanyName $OldCompanyName -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -CompanyName $NewCompanyName - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($NewCompanyName) | Should -BeTrue - $results.Contains(".COMPANYNAME $NewCompanyName") | Should -BeTrue - } - - It "Update .ps1 given Copyright parameter" { - $OldCopyright = "(c) Old Test Corporation" - $NewCopyright = "(c) New Test Corporation" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Copyright $OldCopyright -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Copyright $NewCopyright - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($NewCopyright) | Should -BeTrue - $results.Contains(".COPYRIGHT $NewCopyright") | Should -BeTrue - } - - It "Update .ps1 given RequiredModules parameter" { - $RequiredModuleName = 'PackageManagement' - $OldrequiredModuleVersion = '1.0.0.0' - $OldRequiredModules = @(@{ModuleName = $RequiredModuleName; ModuleVersion = $OldrequiredModuleVersion }) - $NewrequiredModuleVersion = '2.0.0.0' - $NewRequiredModules = @(@{ModuleName = $RequiredModuleName; ModuleVersion = $NewrequiredModuleVersion }) - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredModules $OldRequiredModules -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredModules $NewRequiredModules - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($RequiredModuleName) | Should -BeTrue - $results.Contains($NewrequiredModuleVersion) | Should -BeTrue - $results -like ".REQUIREDMODULES*$RequiredModuleName*$NewrequiredModuleVersion" | Should -BeTrue - } - - It "Update .ps1 given ReleaseNotes parameter" { - $Description = "Test Description" - $OldReleaseNotes = "Old release notes for script." - $NewReleaseNotes = "New release notes for script." - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ReleaseNotes $OldReleaseNotes -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ReleaseNotes $NewReleaseNotes - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewReleaseNotes) | Should -BeTrue - $results -like ".RELEASENOTES*$NewReleaseNotes" | Should -BeTrue - } - - It "Update .ps1 given Tags parameter" { - $Description = "Test Description" - $OldTag1 = "Tag1" - $OldTag2 = "Tag2" - $NewTag1 = "NewTag1" - $NewTag2 = "NewTag2" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Tags $OldTag1, $OldTag2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Tags $NewTag1, $NewTag2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewTag1) | Should -BeTrue - $results.Contains($NewTag2) | Should -BeTrue - $results.Contains(".TAGS $NewTag1 $NewTag2") | Should -BeTrue - } - - It "Update .ps1 given ProjectUri parameter" { - $Description = "Test Description" - $OldProjectUri = "https://www.oldtestprojecturi.com/" - $NewProjectUri = "https://www.newtestprojecturi.com/" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ProjectUri $OldProjectUri -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ProjectUri $NewProjectUri - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewProjectUri) | Should -BeTrue - $results.Contains(".PROJECTURI $NewProjectUri") | Should -BeTrue - } - - It "Update .ps1 given LicenseUri parameter" { - $Description = "Test Description" - $OldLicenseUri = "https://www.oldtestlicenseuri.com/" - $NewLicenseUri = "https://www.newtestlicenseuri.com/" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -LicenseUri $OldLicenseUri -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -LicenseUri $NewLicenseUri - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewLicenseUri) | Should -BeTrue - $results.Contains(".LICENSEURI $NewLicenseUri") | Should -BeTrue - } - - It "Update .ps1 given IconUri parameter" { - $Description = "Test Description" - $OldIconUri = "https://www.oldtesticonuri.com/" - $NewIconUri = "https://www.newtesticonuri.com/" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -IconUri $OldIconUri -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -IconUri $NewIconUri - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewIconUri) | Should -BeTrue - $results.Contains(".ICONURI $NewIconUri") | Should -BeTrue - } - - It "Update .ps1 given ExternalModuleDependencies parameter" { - $Description = "Test Description" - $OldExternalModuleDep1 = "OldExternalModuleDep1" - $OldExternalModuleDep2 = "OldExternalModuleDep2" - $OldExternalModuleDep1FileName = "OldExternalModuleDep1.psm1" - $OldExternalModuleDep2FileName = "OldExternalModuleDep2.psm1" - $OldExternalModuleDepPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldExternalModuleDep1FileName - $OldExternalModuleDepPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldExternalModuleDep2FileName - $null = New-Item -Path $OldExternalModuleDepPath1 -ItemType File -Force - $null = New-Item -Path $OldExternalModuleDepPath2 -ItemType File -Force - - $NewExternalModuleDep1 = "NewExternalModuleDep1" - $NewExternalModuleDep2 = "NewExternalModuleDep2" - $NewExternalModuleDep1FileName = "NewExternalModuleDep1.psm1" - $NewExternalModuleDep2FileName = "NewExternalModuleDep2.psm1" - $NewExternalModuleDepPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewExternalModuleDep1FileName - $NewExternalModuleDepPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewExternalModuleDep2FileName - $null = New-Item -Path $NewExternalModuleDepPath1 -ItemType File -Force - $null = New-Item -Path $NewExternalModuleDepPath2 -ItemType File -Force - - # NOTE: you may need to add the -NestedModules parameter here as well - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ExternalModuleDependencies $OldExternalModuleDep1, $OldExternalModuleDep2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ExternalModuleDependencies $NewExternalModuleDep1, $NewExternalModuleDep2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewExternalModuleDep1) | Should -BeTrue - $results.Contains($NewExternalModuleDep2) | Should -BeTrue - $results -like ".EXTERNALMODULEDEPENDENCIES*$NewExternalModuleDep1*$NewExternalModuleDep2" | Should -BeTrue - } - - It "Update .ps1 given RequiredAssemblies parameter" { - $Description = "Test Description" - $OldRequiredAssembly1 = "OldRequiredAssembly1.dll" - $OldRequiredAssembly2 = "OldRequiredAssembly2.dll" - $OldRequiredAssemblyPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldRequiredAssembly1 - $OldRequiredAssemblyPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldRequiredAssembly2 - $null = New-Item -Path $OldRequiredAssemblyPath1 -ItemType File -Force - $null = New-Item -Path $OldRequiredAssemblyPath2 -ItemType File -Force - - $NewRequiredAssembly1 = "NewRequiredAssembly1.dll" - $NewRequiredAssembly2 = "NewRequiredAssembly2.dll" - $NewRequiredAssemblyPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewRequiredAssembly1 - $NewRequiredAssemblyPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewRequiredAssembly2 - $null = New-Item -Path $NewRequiredAssemblyPath1 -ItemType File -Force - $null = New-Item -Path $NewRequiredAssemblyPath2 -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredAssemblies $OldRequiredAssembly1, $OldRequiredAssembly2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredAssemblies $NewRequiredAssembly1, $NewRequiredAssembly2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewRequiredAssembly1) | Should -BeTrue - $results.Contains($NewRequiredAssembly2) | Should -BeTrue - $results -like ".REQUIREDASSEMBLIES*$NewRequiredAssembly1*$NewRequiredAssembly2" | Should -BeTrue - } - - It "Update .ps1 given NestedModules parameter" { - $Description = "Test Description" - $OldNestedModule1 = "OldNestedModule1" - $OldNestedModule2 = "OldNestedModule2" - $OldNestModuleFileName1 = "OldNestedModule1.dll" - $OldNestModuleFileName2 = "OldNestedModule2.dll" - $OldNestedModulePath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldNestModuleFileName1 - $OldNestedModulePath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldNestModuleFileName2 - $null = New-Item -Path $OldNestedModulePath1 -ItemType File -Force - $null = New-Item -Path $OldNestedModulePath2 -ItemType File -Force - - $NewNestedModule1 = "NewNestedModule1" - $NewNestedModule2 = "NewNestedModule2" - $NewNestModuleFileName1 = "NewNestedModule1.dll" - $NewNestModuleFileName2 = "NewNestedModule2.dll" - $NewNestedModulePath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewNestModuleFileName1 - $NewNestedModulePath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewNestModuleFileName2 - $null = New-Item -Path $NewNestedModulePath1 -ItemType File -Force - $null = New-Item -Path $NewNestedModulePath2 -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -NestedModules $OldNestedModule1, $OldNestedModule2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -NestedModules $NewNestedModule1, $NewNestedModule2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewNestedModule1) | Should -BeTrue - $results.Contains($NewNestedModule2) | Should -BeTrue - $results -like ".NESTEDMODULES*$NewNestedModule1*$NewNestedModule2" | Should -BeTrue - } - - It "Update .ps1 given RequiredScripts parameter" { - $Description = "Test Description" - $OldRequiredScript1 = "OldNestedModule1.ps1" - $OldRequiredScript2 = "OldNestedModule2.ps1" - $OldRequiredScript1Path = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldRequiredScript1 - $OldRequiredScript2Path = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldRequiredScript2 - $null = New-Item -Path $OldRequiredScript1Path -ItemType File -Force - $null = New-Item -Path $OldRequiredScript2Path -ItemType File -Force - - $NewRequiredScript1 = "NewNestedModule1.ps1" - $NewRequiredScript2 = "NewNestedModule2.ps1" - $NewRequiredScript1Path = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewRequiredScript1 - $NewRequiredScript2Path = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewRequiredScript2 - $null = New-Item -Path $NewRequiredScript1Path -ItemType File -Force - $null = New-Item -Path $NewRequiredScript2Path -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredScripts $OldRequiredScript1, $OldRequiredScript2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredScripts $NewRequiredScript1, $NewRequiredScript2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewRequiredScript1) | Should -BeTrue - $results.Contains($NewRequiredScript2) | Should -BeTrue - $results -like ".REQUIREDSCRIPTS*$NewRequiredScript1*$NewRequiredScript2" | Should -BeTrue - } - - It "Update .ps1 given ExternalScriptDependencies parameter" { - $Description = "Test Description" - $OldExternalScriptDep1 = "OldExternalScriptDep1.ps1" - $OldExternalScriptDep2 = "OldExternalScriptDep2.ps1" - $OldExternalScriptDepPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldExternalScriptDep1 - $OldExternalScriptDepPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldExternalScriptDep2 - $null = New-Item -Path $OldExternalScriptDepPath1 -ItemType File -Force - $null = New-Item -Path $OldExternalScriptDepPath2 -ItemType File -Force - - $NewExternalScriptDep1 = "NewExternalScriptDep1.ps1" - $NewExternalScriptDep2 = "NewExternalScriptDep2.ps1" - $NewExternalScriptDepPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewExternalScriptDep1 - $NewExternalScriptDepPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewExternalScriptDep2 - $null = New-Item -Path $NewExternalScriptDepPath1 -ItemType File -Force - $null = New-Item -Path $NewExternalScriptDepPath2 -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ExternalModuleDependencies $OldExternalModuleDep1, $OldExternalModuleDep2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ExternalModuleDependencies $NewExternalModuleDep1, $NewExternalModuleDep2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewExternalModuleDep1) | Should -BeTrue - $results.Contains($NewExternalModuleDep2) | Should -BeTrue - $results -like ".EXTERNALSCRIPTDEPENDENCIES*$NewExternalModuleDep1*$NewExternalModuleDep2" | Should -BeTrue - } - - It "Update .ps1 given PrivateData parameter" { - $Description = "Test Description" - $OldPrivateData = @{"OldPrivateDataEntry1" = "OldPrivateDataValue1"} - $NewPrivateData = @{"NewPrivateDataEntry1" = "NewPrivateDataValue1"} - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -PrivateData $OldPrivateData -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -PrivateData $NewPrivateData - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewPrivateData) | Should -BeTrue - $results -like ".PRIVATEDATA*$NewPrivateData" | Should -BeTrue - } - - It "Update signed script when using RemoveSignature parameter" { - $scriptName = "ScriptWithSignature.ps1" - $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName - - # use a copy of the signed script file so we can re-use for other tests - $null = Copy-Item -Path $scriptFilePath -Destination $TestDrive - $tmpScriptFilePath = Join-Path -Path $TestDrive -ChildPath $scriptName - - Update-PSScriptFileInfo -FilePath $tmpScriptFilePath -Version "2.0.0.0" -RemoveSignature - Test-PSScriptFileInfo -FilePath $tmpScriptFilePath | Should -Be $true - } - - It "Throw error when attempting to update a signed script without -RemoveSignature parameter" { - $scriptName = "ScriptWithSignature.ps1" - $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName - - # use a copy of the signed script file so we can re-use for other tests - $null = Copy-Item -Path $scriptFilePath -Destination $TestDrive - $tmpScriptFilePath = Join-Path -Path $TestDrive -ChildPath $scriptName - - { Update-PSScriptFileInfo -FilePath $tmpScriptFilePath -Version "2.0.0.0" } | Should -Throw -ErrorId "ScriptToBeUpdatedContainsSignature,Microsoft.PowerShell.PowerShellGet.Cmdlets.UpdatePSScriptFileInfo" - } -#> -} \ No newline at end of file diff --git a/test/testFiles/testScripts/ScriptWithSignature.ps1 b/test/testFiles/testScripts/ScriptWithSignature.ps1 new file mode 100644 index 000000000..9bd193af7 --- /dev/null +++ b/test/testFiles/testScripts/ScriptWithSignature.ps1 @@ -0,0 +1,75 @@ + +<#PSScriptInfo + +.VERSION 1.0 + +.GUID 3951be04-bd06-4337-8dc3-a620bf539fbd + +.AUTHOR annavied + +.COMPANYNAME + +.COPYRIGHT + +.TAGS + +.LICENSEURI + +.PROJECTURI + +.ICONURI + +.EXTERNALMODULEDEPENDENCIES + +.REQUIREDSCRIPTS + +.EXTERNALSCRIPTDEPENDENCIES + +.RELEASENOTES + +.PRIVATEDATA + +#> + +<# + +.DESCRIPTION + this is a test for a script that will be published remotely + +#> +Param() + + + +# SIG # Begin signature block +# MIIFbQYJKoZIhvcNAQcCoIIFXjCCBVoCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB +# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR +# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUhY04RvNe0Q8hliL7qS3/X9kr +# QVugggMIMIIDBDCCAeygAwIBAgIQN+zCRZRKiphJ5gGoRKvpeTANBgkqhkiG9w0B +# AQsFADAaMRgwFgYDVQQDDA9Db2RlU2lnbmluZ0NlcnQwHhcNMjIwNjIyMTgyODUx +# WhcNMjQwNjIyMTgzODUwWjAaMRgwFgYDVQQDDA9Db2RlU2lnbmluZ0NlcnQwggEi +# MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf7gQAR4AVpVc4/4OlffaEQ6uE +# klG01+ga7sZbV7z9UkJFIDbntapCoXV85w/bNbmWSI+IUDisVBS7BIoicKagHskE +# YhRJv6WL/zxD2lWP21MRkEJBEMicbrj38F2R/khGDq/T5/a1XH+7QVAsf1kOG/oU +# d0CUDqgsR5+JdpaMt/QRM/jFLEUdvs+7zCvduciEEQRFFUbYYqy9RfmxMpPxZ6CM +# RjLVr5k4tirbg1YyBK6l7xPvT3BUejGvEYPOdAskPXMVbMO37DyEszudqOz9eEvp +# yHCKOgePLeq+9DbOQ+fAy30c79YNU5JfvgaDY+3c99WQXSeQuLYNDUeDDPGxAgMB +# AAGjRjBEMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNV +# HQ4EFgQUrmtlJTMGV5h8ksEMzPTPYk04g3IwDQYJKoZIhvcNAQELBQADggEBAAR3 +# sIiiVgSxUiPzGS/Ivwgjvqfsb6HXioE9VIJxQPwXc63LqC01TGJpeGayjr5zQ4p5 +# vt9q8WsiZvoUMofWzabz4BdprGWVDrO8hwksIixF8ojbfLuAra1cZ4qkDZtJH2Sn +# 0dUhvXabZqLuVghMiyqcSvs2hN8OiVI+tLzW8VQKzbFdj77c+lHxKBTkcKVpLiSI +# V2V8P4zRxyYE+CMlpTr58ErOGVxP1zITou7fwCAXdWEKWo5nlU22VNF6oGE9tghm +# S3M5PQT8lFCjZOPPKx+0oLDxwjluHENXZzH+61ugrszzRjK1rG3D3emrRYh/4BcG +# Wy7J1H41povt21JlzEExggHPMIIBywIBATAuMBoxGDAWBgNVBAMMD0NvZGVTaWdu +# aW5nQ2VydAIQN+zCRZRKiphJ5gGoRKvpeTAJBgUrDgMCGgUAoHgwGAYKKwYBBAGC +# NwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUbfriqhB/ +# EKzgoXnVu2UFtaTb040wDQYJKoZIhvcNAQEBBQAEggEAiQa/HUhDP1HyrPh7mC5H +# 6IwOdxL4p3EIkGeuUh3ZqWRNFLNz0ob24vqmKBtaKTfJqqrxTIBYeoBKB3Y8Wcx2 +# rEaH31WqQM2U7mFvM2cVv6dcrdWmLcMwi3LSEMxJf6VbWpbmWZK6zMRW2H76P5wQ +# cs6BUOwKZq/5eQcQLjJ3h+Mh5dsENZ7scB4U1yihD7Ggvrgxf54+J/TS8XuDsx2o +# g0czxIjMBwT5wGh8BqbC50izZ3D0WRFe7UNnhMk7zKG/bvIRBxah+JV25hdoGYaR +# 2tdmgr4EMPoB1ti8DOFmYAicckDWfX7/X4NzeM234LSMLtOxO2lVj5jhkmJJdjKh +# WA== +# SIG # End signature block \ No newline at end of file From 5f8c7d453a079ef190842359730f974b6e32ee1a Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 11:39:02 -0400 Subject: [PATCH 07/29] code review feedback- ensure methods have summary blocks ending in period --- src/code/PSScriptContents.cs | 12 +++++++----- src/code/PSScriptFileInfo.cs | 15 ++++++++------- src/code/PSScriptHelp.cs | 23 ++++++++++++++++++++++- src/code/PSScriptMetadata.cs | 18 ++++++++++++------ src/code/PSScriptRequires.cs | 10 ++++++++-- src/code/TestPSScriptFileInfo.cs | 2 +- 6 files changed, 58 insertions(+), 22 deletions(-) diff --git a/src/code/PSScriptContents.cs b/src/code/PSScriptContents.cs index a55b77596..3aa121d64 100644 --- a/src/code/PSScriptContents.cs +++ b/src/code/PSScriptContents.cs @@ -31,7 +31,7 @@ public sealed class PSScriptContents /// /// End of file contents for the .ps1 file. /// - public bool ContainsSignature { get; set; } + public bool ContainsSignature { get; set; } = false; #endregion @@ -55,6 +55,10 @@ internal PSScriptContents() {} #region Internal Methods + /// + /// Parses end of file contents as a string from the file lines passed in + /// and sets property indicating whether those contents contain a signature. + /// internal void ParseContent(string[] commentLines) { if (commentLines.Length != 0) @@ -66,9 +70,9 @@ internal void ParseContent(string[] commentLines) /// /// This function would be called by PSScriptFileInfo.TryCreateScriptFileInfoString(), - /// called by New-PSScriptFileInfo cmdlet (in which case EndOfFileContents is an empty string so there's no signature that'll get removed) + /// by the New-PSScriptFileInfo cmdlet (in which case EndOfFileContents is an empty string so there's no signature that'll get removed) /// or by Update-PSScriptFileInfo cmdlet (in which case EndOfFileContents may not be empty and may contain a signature. - /// and the Update cmdlet checks for -RemoveSignature before control reaches this method) + /// The Update cmdlet checks for -RemoveSignature before control reaches this method). /// internal string EmitContent() { @@ -76,8 +80,6 @@ internal string EmitContent() return EndOfFileContents; } - - #endregion #region Private Methods diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 629802164..1e6e78a71 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -22,7 +22,7 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses public sealed class PSScriptFileInfo { #region Properties - public PSScriptMetadata ScriptMetadataCommment { get; set; } + public PSScriptMetadata ScriptMetadataComment { get; set; } public PSScriptHelp ScriptHelpComment { get; set; } @@ -72,7 +72,7 @@ public PSScriptFileInfo( PSScriptRequires scriptRequiresComment = new PSScriptRequires(requiredModules); PSScriptContents scriptRemainingContent = new PSScriptContents(String.Empty); - this.ScriptMetadataCommment = scriptMetadataComment; + this.ScriptMetadataComment = scriptMetadataComment; this.ScriptHelpComment = scriptHelpComment; this.ScriptRequiresComment = scriptRequiresComment; this.ScriptContent = scriptRemainingContent; @@ -85,7 +85,7 @@ public PSScriptFileInfo( PSScriptContents scriptRemainingContent ) { - this.ScriptMetadataCommment = scriptMetadataComment; + this.ScriptMetadataComment = scriptMetadataComment; this.ScriptHelpComment = scriptHelpComment; this.ScriptRequiresComment = scriptRequiresComment; this.ScriptContent = scriptRemainingContent; @@ -98,6 +98,7 @@ PSScriptContents scriptRemainingContent /// /// Tests .ps1 file for validity /// + // tODO: separate out where can into 4 internal static bool TryParseScriptFile( string scriptFileInfoPath, out PSScriptFileInfo parsedScript, @@ -324,7 +325,7 @@ internal static bool TryUpdateScriptFileContents( return false; } - if (!scriptInfo.ScriptMetadataCommment.UpdateContent( + if (!scriptInfo.ScriptMetadataComment.UpdateContent( version: version, guid: guid, author: author, @@ -347,7 +348,7 @@ internal static bool TryUpdateScriptFileContents( if (!scriptInfo.ScriptHelpComment.UpdateContent( description: description, - out ErrorRecord helpUpdateError)) // todo: check v2 if other things can be updated? like Example? + out ErrorRecord helpUpdateError)) { errorsList.Add(helpUpdateError); successfullyUpdated = false; @@ -395,7 +396,7 @@ out ErrorRecord[] errors bool fileContentsSuccessfullyCreated = true; // step 1: validate object properties for required script properties - if (!ScriptMetadataCommment.ValidateContent(out ErrorRecord[] metadataValidationErrors)) + if (!ScriptMetadataComment.ValidateContent(out ErrorRecord[] metadataValidationErrors)) { errorsList.AddRange(metadataValidationErrors); fileContentsSuccessfullyCreated = false; @@ -414,7 +415,7 @@ out ErrorRecord[] errors } // step 2: create string that will be used to write later - psScriptFileString = ScriptMetadataCommment.EmitContent(); + psScriptFileString = ScriptMetadataComment.EmitContent(); string psRequiresCommentBlock = ScriptRequiresComment.EmitContent(); if (!String.IsNullOrEmpty(psRequiresCommentBlock)) diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs index d8a1a4dfd..5812d6401 100644 --- a/src/code/PSScriptHelp.cs +++ b/src/code/PSScriptHelp.cs @@ -112,6 +112,10 @@ internal PSScriptHelp() {} #region Internal Methods + /// + /// Parses HelpInfo metadata out of the HelpInfo comment lines found while reading the file + /// and populates PSScriptHelp properties from that metadata. + /// internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord error) { bool successfullyParsed = true; @@ -141,6 +145,9 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord error) return successfullyParsed; } + /// + /// Helper method that parses metadata out of of comment block's lines (which are passed in) into a hashtable. + /// internal Hashtable ParseContent(string[] commentLines) { Hashtable parsedHelpMetadata = new Hashtable(); @@ -191,6 +198,10 @@ internal Hashtable ParseContent(string[] commentLines) return parsedHelpMetadata; } + /// + /// Valides parsed help info content from the hashtable to ensure required help metadata (Description) is present + /// and does not contain empty values. + /// internal bool ValidateParsedContent(Hashtable parsedHelpMetadata, out ErrorRecord error) { error = null; @@ -215,6 +226,10 @@ internal bool ValidateParsedContent(Hashtable parsedHelpMetadata, out ErrorRecor return true; } + /// + /// Validates help info properties contain required script Help properties + /// i.e Description. + /// internal bool ValidateContent(out ErrorRecord error) { error = null; @@ -239,6 +254,9 @@ internal bool ValidateContent(out ErrorRecord error) return true; } + /// + /// Emits string representation of 'HelpInfo <# ... #>' comment and its metadata contents. + /// internal string EmitContent() { string psHelpInfo; @@ -299,6 +317,9 @@ internal string EmitContent() return psHelpInfo; } + /// + /// Updates contents of the HelpInfo properties from any (non-default) values passed in. + /// internal bool UpdateContent(string description, out ErrorRecord error) { error = null; @@ -334,7 +355,7 @@ internal bool UpdateContent(string description, out ErrorRecord error) #region Private Methods /// - /// Ensure description field (passed as stringToValidate) does not contain '<#' or '#>' + /// Ensure description field (passed as stringToValidate) does not contain '<#' or '#>'. /// private bool StringContainsComment(string stringToValidate) { diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs index 49e4655b8..d6661cbb3 100644 --- a/src/code/PSScriptMetadata.cs +++ b/src/code/PSScriptMetadata.cs @@ -143,7 +143,7 @@ internal PSScriptMetadata() {} /// /// Parses script metadata comment (passed in as its lines) into PSScriptMetadata instance's properties - /// Also validates that this metadata has required script properties + /// Also validates that this metadata has required script properties. /// internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] errors, out string[] msgs) { @@ -222,7 +222,7 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error } /// - /// Helper method that parses metadata out of of comment block's lines (which are passed in) into a hashtable + /// Helper method that parses metadata out of of comment block's lines (which are passed in) into a hashtable. /// internal Hashtable ParseContent(string[] commentLines) { @@ -311,6 +311,10 @@ some data } + /// + /// Valides parsed metadata content from the hashtable to ensure required metadata (Author, Version, Guid) is present + /// and does not contain empty values. + /// internal bool ValidateParsedContent(Hashtable parsedMetadata, out ErrorRecord[] errors) { List errorsList = new List(); @@ -343,8 +347,8 @@ internal bool ValidateParsedContent(Hashtable parsedMetadata, out ErrorRecord[] return errors.Length == 0; } /// - /// Validates metadata content parsed from .ps1 is valid and contains required script properties - /// i.e Author, Version, Guid + /// Validates metadata properties are valid and contains required script properties + /// i.e Author, Version, Guid. /// internal bool ValidateContent(out ErrorRecord[] errors) { @@ -383,7 +387,7 @@ internal bool ValidateContent(out ErrorRecord[] errors) } /// - /// Emits string representation of '<#PSScriptInfo ... #>' comment and its metadata contents + /// Emits string representation of '<#PSScriptInfo ... #>' comment and its metadata contents. /// internal string EmitContent() { @@ -435,7 +439,9 @@ Feature 5 return String.Join("\n\n", psScriptInfoLines); } - + /// + /// Updates contents of the script metadata properties from any (non-default) values passed in. + /// internal bool UpdateContent( string version, Guid guid, diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs index 352eac7ec..2c437e5ed 100644 --- a/src/code/PSScriptRequires.cs +++ b/src/code/PSScriptRequires.cs @@ -45,7 +45,7 @@ internal PSScriptRequires() {} #region Internal Methods /// - /// Parses RequiredModules out of comment lines and validates during + /// Parses RequiredModules out of comment lines and validates during parse process. /// internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] errors) { @@ -75,7 +75,7 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error { foreach (ParseError err in parserErrors) { - var message = String.Format("Could not requires comments as valid PowerShell input due to {1}.", err.Message); + var message = String.Format("Could not requires comments as valid PowerShell input due to {0}.", err.Message); var ex = new InvalidOperationException(message); var requiresCommentParseError = new ErrorRecord(ex, err.ErrorId, ErrorCategory.ParserError, null); errorsList.Add(requiresCommentParseError); @@ -107,6 +107,9 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error return true; } + /// + /// Emits string representation of '#Requires ...' comment(s). + /// internal string EmitContent() { if (RequiredModules.Length > 0) @@ -125,6 +128,9 @@ internal string EmitContent() return String.Empty; } + /// + /// Updates the current Requires content with another (passed in), effectively replaces it. + /// internal void UpdateContent(ModuleSpecification[] requiredModules) { if (requiredModules != null && requiredModules.Length != 0){ diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 5922bf0ba..4ed3e50cd 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -80,7 +80,7 @@ protected override void EndProcessing() WriteWarning(msg); } - WriteObject(isValidScript); + WriteObject(isValidScript); } #endregion From faf9704518725fd98513af4073b4d19b7411b3ea Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 11:47:30 -0400 Subject: [PATCH 08/29] refactor method into Utils --- src/code/PSScriptHelp.cs | 55 +-------------------- src/code/PSScriptMetadata.cs | 93 +----------------------------------- src/code/Utils.cs | 70 ++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 146 deletions(-) diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs index 5812d6401..5fdb7484b 100644 --- a/src/code/PSScriptHelp.cs +++ b/src/code/PSScriptHelp.cs @@ -123,7 +123,7 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord error) char[] newlineDelimeter = new char[]{'\n'}; // parse content into a hashtable - Hashtable parsedHelpMetadata = ParseContent(commentLines); + Hashtable parsedHelpMetadata = Utils.ParseCommentBlockContent(commentLines); if (!ValidateParsedContent(parsedHelpMetadata, out error)) { @@ -145,59 +145,6 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord error) return successfullyParsed; } - /// - /// Helper method that parses metadata out of of comment block's lines (which are passed in) into a hashtable. - /// - internal Hashtable ParseContent(string[] commentLines) - { - Hashtable parsedHelpMetadata = new Hashtable(); - string keyName = ""; - string value = ""; - - for (int i = 1; i < commentLines.Count(); i++) - { - string line = commentLines[i]; - - // scenario where line is: .KEY VALUE - // this line contains a new metadata property. - if (line.Trim().StartsWith(".")) - { - // check if keyName was previously populated, if so add this key value pair to the metadata hashtable - if (!String.IsNullOrEmpty(keyName)) - { - parsedHelpMetadata.Add(keyName, value); - } - - string[] parts = line.Trim().TrimStart('.').Split(); - keyName = parts[0]; - value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; - } - else if (!String.IsNullOrEmpty(line)) - { - // scenario where line contains text that is a continuation of value from previously recorded key - // this line does not starting with .KEY, and is also not an empty line. - if (value.Equals(String.Empty)) - { - value += line; - } - else - { - value += Environment.NewLine + line; - } - } - } - - // this is the case where last key value had multi-line value. - // and we've captured it, but still need to add it to hashtable. - if (!String.IsNullOrEmpty(keyName) && !parsedHelpMetadata.ContainsKey(keyName)) - { - // only add this key value if it hasn't already been added - parsedHelpMetadata.Add(keyName, value); - } - - return parsedHelpMetadata; - } - /// /// Valides parsed help info content from the hashtable to ensure required help metadata (Description) is present /// and does not contain empty values. diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs index d6661cbb3..633cf3066 100644 --- a/src/code/PSScriptMetadata.cs +++ b/src/code/PSScriptMetadata.cs @@ -151,7 +151,8 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error List msgsList = new List(); // parse content into a hashtable - Hashtable parsedMetadata = ParseContent(commentLines); + // Hashtable parsedMetadata = ParseContent(commentLines); + Hashtable parsedMetadata = Utils.ParseCommentBlockContent(commentLines); if (parsedMetadata.Count == 0) { @@ -221,96 +222,6 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error return true; } - /// - /// Helper method that parses metadata out of of comment block's lines (which are passed in) into a hashtable. - /// - internal Hashtable ParseContent(string[] commentLines) - { - /** - comment lines will look like this: - - .VERSION 1.0 - - .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd - - .AUTHOR - - .COMPANYNAME - - .COPYRIGHT - - .TAGS - - .LICENSEURI - - .PROJECTURI - - .ICONURI - - .EXTERNALMODULEDEPENDENCIES - - .REQUIREDSCRIPTS - - .EXTERNALSCRIPTDEPENDENCIES - - .RELEASENOTES - some notes - - .PRIVATEDATA - some data - - */ - - Hashtable parsedMetadata = new Hashtable(); - - string keyName = ""; - string value = ""; - - for (int i = 1; i < commentLines.Count(); i++) - { - string line = commentLines[i]; - - // scenario where line is: .KEY VALUE - // this line contains a new metadata property. - if (line.Trim().StartsWith(".")) - { - // check if keyName was previously populated, if so add this key value pair to the metadata hashtable - if (!String.IsNullOrEmpty(keyName)) - { - parsedMetadata.Add(keyName, value); - } - - string[] parts = line.Trim().TrimStart('.').Split(); - keyName = parts[0]; - value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; - } - else if (!String.IsNullOrEmpty(line)) - { - // scenario where line contains text that is a continuation of value from previously recorded key - // this line does not starting with .KEY, and is also not an empty line. - if (value.Equals(String.Empty)) - { - value += line; - } - else - { - value += Environment.NewLine + line; - } - } - } - - // this is the case where last key value had multi-line value. - // and we've captured it, but still need to add it to hashtable. - if (!String.IsNullOrEmpty(keyName) && !parsedMetadata.ContainsKey(keyName)) - { - // only add this key value if it hasn't already been added - parsedMetadata.Add(keyName, value); - } - - return parsedMetadata; - } - - /// /// Valides parsed metadata content from the hashtable to ensure required metadata (Author, Version, Guid) is present /// and does not contain empty values. diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 736e02392..4e1ae6692 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -926,7 +926,7 @@ public static Hashtable ConvertJsonToHashtable( return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; } - public static bool TryCreateModuleSpecification( + public static bool TryCreateModuleSpecification( Hashtable[] moduleSpecHashtables, out ModuleSpecification[] validatedModuleSpecs, out ErrorRecord[] errors) @@ -1037,6 +1037,74 @@ public static bool TryCreateModuleSpecification( return moduleSpecCreatedSuccessfully; } + /// + /// Parses metadata out of a comment block's lines (which are passed in) into a hashtable. + /// + public static Hashtable ParseCommentBlockContent(string[] commentLines) + { + /** + Comment lines can look like this: + + .KEY1 value + + .KEY2 value + + .KEY3 + value + + .KEY4 value + value continued + + */ + + Hashtable parsedHelpMetadata = new Hashtable(); + string keyName = ""; + string value = ""; + + for (int i = 1; i < commentLines.Count(); i++) + { + string line = commentLines[i]; + + // scenario where line is: .KEY VALUE + // this line contains a new metadata property. + if (line.Trim().StartsWith(".")) + { + // check if keyName was previously populated, if so add this key value pair to the metadata hashtable + if (!String.IsNullOrEmpty(keyName)) + { + parsedHelpMetadata.Add(keyName, value); + } + + string[] parts = line.Trim().TrimStart('.').Split(); + keyName = parts[0]; + value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; + } + else if (!String.IsNullOrEmpty(line)) + { + // scenario where line contains text that is a continuation of value from previously recorded key + // this line does not starting with .KEY, and is also not an empty line. + if (value.Equals(String.Empty)) + { + value += line; + } + else + { + value += Environment.NewLine + line; + } + } + } + + // this is the case where last key value had multi-line value. + // and we've captured it, but still need to add it to hashtable. + if (!String.IsNullOrEmpty(keyName) && !parsedHelpMetadata.ContainsKey(keyName)) + { + // only add this key value if it hasn't already been added + parsedHelpMetadata.Add(keyName, value); + } + + return parsedHelpMetadata; + } + #endregion #region Directory and File From 5506e6c6e3b03151797a8d28f56ae6510c045e04 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 12:40:43 -0400 Subject: [PATCH 09/29] refactor TryParsePSScript() to use helpers --- src/code/PSScriptFileInfo.cs | 155 ++++++++++++++++++++--------- src/code/TestPSScriptFileInfo.cs | 2 +- src/code/UpdatePSScriptFileInfo.cs | 2 +- 3 files changed, 111 insertions(+), 48 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 1e6e78a71..a2d049cf8 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -96,30 +96,27 @@ PSScriptContents scriptRemainingContent #region Internal Static Methods /// - /// Tests .ps1 file for validity + /// Parses .ps1 file contents for PSScriptInfo, PSHelpInfo, Requires comments /// - // tODO: separate out where can into 4 - internal static bool TryParseScriptFile( + internal static bool TryParseScriptFileContents( string scriptFileInfoPath, - out PSScriptFileInfo parsedScript, - out ErrorRecord[] errors, - out string[] verboseMsgs // this is for Uri errors, which aren't required by script but we check if those in the script aren't valid Uri's. - ) + ref List psScriptInfoCommentContent, + ref List helpInfoCommentContent, + ref List requiresCommentContent, + ref string[] remainingFileContent, + out ErrorRecord error) { - verboseMsgs = new string[]{}; - List errorsList = new List(); - parsedScript = null; + error= null; - string[] fileContents = File.ReadAllLines(scriptFileInfoPath); + psScriptInfoCommentContent = new List(); + helpInfoCommentContent = new List(); + requiresCommentContent = new List(); + remainingFileContent = new string[]{}; - List psScriptInfoCommentContent = new List(); - List helpInfoCommentContent = new List(); - List requiresContent = new List(); - string[] remainingFileContentArray = new string[]{}; + string[] fileContents = File.ReadAllLines(scriptFileInfoPath); - bool gotEndToPSSCriptInfoContent = false; - bool gotEndToHelpInfoContent = false; - bool parsedContentSuccessfully = true; + bool reachedPSSCriptInfoCommentEnd = false; + bool reachedHelpInfoCommentEnd = false; int i = 0; int endOfFileContentsStartIndex = 0; @@ -136,7 +133,7 @@ internal static bool TryParseScriptFile( string blockLine = fileContents[j]; if (blockLine.StartsWith("#>")) { - gotEndToPSSCriptInfoContent = true; + reachedPSSCriptInfoCommentEnd = true; i = j + 1; break; } @@ -145,12 +142,11 @@ internal static bool TryParseScriptFile( j++; } - if (!gotEndToPSSCriptInfoContent) + if (!reachedPSSCriptInfoCommentEnd) { var message = String.Format("Could not parse '{0}' as a PowerShell script file due to missing the closing '#>' for <#PSScriptInfo comment block", scriptFileInfoPath); var ex = new InvalidOperationException(message); - var missingEndBracketToPSScriptInfoParseError = new ErrorRecord(ex, "MissingEndBracketToPSScriptInfoParseError", ErrorCategory.ParserError, null); - errors = new ErrorRecord[]{missingEndBracketToPSScriptInfoParseError}; + error = new ErrorRecord(ex, "MissingEndBracketToPSScriptInfoParseError", ErrorCategory.ParserError, null); return false; } } @@ -164,7 +160,7 @@ internal static bool TryParseScriptFile( string blockLine = fileContents[j]; if (blockLine.StartsWith("#>")) { - gotEndToHelpInfoContent = true; + reachedHelpInfoCommentEnd = true; i = j + 1; endOfFileContentsStartIndex = i; break; @@ -174,18 +170,17 @@ internal static bool TryParseScriptFile( j++; } - if (!gotEndToHelpInfoContent) + if (!reachedHelpInfoCommentEnd) { var message = String.Format("Could not parse '{0}' as a PowerShell script file due to missing the closing '#>' for HelpInfo comment block", scriptFileInfoPath); var ex = new InvalidOperationException(message); - var missingEndBracketToHelpInfoCommentParseError = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); - errors = new ErrorRecord[]{missingEndBracketToHelpInfoCommentParseError}; + error = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); return false; } } else if (line.StartsWith("#Requires")) { - requiresContent.Add(line); + requiresCommentContent.Add(line); i++; } else if (endOfFileContentsStartIndex != 0) @@ -202,8 +197,8 @@ internal static bool TryParseScriptFile( if (endOfFileContentsStartIndex != 0 && (endOfFileContentsStartIndex < fileContents.Length)) { // from this line to fileContents.Length is the endOfFileContents, if any - remainingFileContentArray = new string[fileContents.Length - endOfFileContentsStartIndex]; - Array.Copy(fileContents, endOfFileContentsStartIndex, remainingFileContentArray, 0, (fileContents.Length - endOfFileContentsStartIndex)); + remainingFileContent = new string[fileContents.Length - endOfFileContentsStartIndex]; + Array.Copy(fileContents, endOfFileContentsStartIndex, remainingFileContent, 0, (fileContents.Length - endOfFileContentsStartIndex)); } if (psScriptInfoCommentContent.Count() == 0) @@ -211,8 +206,7 @@ internal static bool TryParseScriptFile( // check for file not containing '<#PSScriptInfo ... #>' comment var message = String.Format("Could not parse '{0}' as a PowerShell script due to it missing '<#PSScriptInfo #> block", scriptFileInfoPath); var ex = new InvalidOperationException(message); - var missingPSScriptInfoCommentError = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); - errors = new ErrorRecord[]{missingPSScriptInfoCommentError}; + error = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); return false; } @@ -221,13 +215,34 @@ internal static bool TryParseScriptFile( // check for file not containing HelpInfo comment var message = String.Format("Could not parse '{0}' as a PowerShell script due to it missing HelpInfo comment block", scriptFileInfoPath); var ex = new InvalidOperationException(message); - var missingHelpInfoCommentError = new ErrorRecord(ex, "missingHelpInfoCommentError", ErrorCategory.ParserError, null); - errors = new ErrorRecord[]{missingHelpInfoCommentError}; + error = new ErrorRecord(ex, "missingHelpInfoCommentError", ErrorCategory.ParserError, null); return false; } - // now populate PSScriptFileInfo object by first creating instances for the property objects - PSScriptMetadata currentMetadata = new PSScriptMetadata(); + return true; + } + + /// + /// Populates script info classes (PSScriptMetadata, PSScriptHelp, PSScriptRequires, PSScriptContents) with previosuly + /// parsed metadata from the ps1 file. + /// + internal static bool TryPopulateScriptClassesWithParsedContent( + List psScriptInfoCommentContent, + List helpInfoCommentContent, + List requiresCommentContent, + string[] remainingFileContent, + out PSScriptMetadata currentMetadata, + out PSScriptHelp currentHelpInfo, + out PSScriptRequires currentRequiresComment, + out PSScriptContents currentEndOfFileContents, + out ErrorRecord[] errors, + out string[] verboseMsgs) + { + List errorsList = new List(); + + bool parsedContentSuccessfully = true; + + currentMetadata = new PSScriptMetadata(); if (!currentMetadata.ParseContentIntoObj( commentLines: psScriptInfoCommentContent.ToArray(), out ErrorRecord[] metadataErrors, @@ -237,7 +252,7 @@ internal static bool TryParseScriptFile( parsedContentSuccessfully = false; } - PSScriptHelp currentHelpInfo = new PSScriptHelp(); + currentHelpInfo = new PSScriptHelp(); if (!currentHelpInfo.ParseContentIntoObj( commentLines: helpInfoCommentContent.ToArray(), out ErrorRecord helpError)) @@ -246,24 +261,72 @@ internal static bool TryParseScriptFile( parsedContentSuccessfully = false; } - PSScriptRequires currentRequiresComment = new PSScriptRequires(); + currentRequiresComment = new PSScriptRequires(); if (!currentRequiresComment.ParseContentIntoObj( - commentLines: requiresContent.ToArray(), + commentLines: requiresCommentContent.ToArray(), out ErrorRecord[] requiresErrors)) { errorsList.AddRange(requiresErrors); parsedContentSuccessfully = false; } - PSScriptContents currentEndOfFileContents = new PSScriptContents(); - currentEndOfFileContents.ParseContent(commentLines: remainingFileContentArray); + currentEndOfFileContents = new PSScriptContents(); + currentEndOfFileContents.ParseContent(commentLines: remainingFileContent); + + errors = errorsList.ToArray(); + return parsedContentSuccessfully; + } + + /// + /// Tests .ps1 file for validity + /// + internal static bool TryTestPSScriptFile( + string scriptFileInfoPath, + out PSScriptFileInfo parsedScript, + out ErrorRecord[] errors, + out string[] verboseMsgs // this is for Uri errors, which aren't required by script but we check if those in the script aren't valid Uri's. + ) + { + verboseMsgs = new string[]{}; + List errorsList = new List(); + parsedScript = null; + + List psScriptInfoCommentContent = new List(); + List helpInfoCommentContent = new List(); + List requiresCommentContent = new List(); + string[] remainingFileContent = new string[]{}; + + // parse .ps1 contents out of file into list objects + if (!TryParseScriptFileContents( + scriptFileInfoPath: scriptFileInfoPath, + psScriptInfoCommentContent: ref psScriptInfoCommentContent, + helpInfoCommentContent: ref helpInfoCommentContent, + requiresCommentContent: ref requiresCommentContent, + remainingFileContent: ref remainingFileContent, + out ErrorRecord parseError)) + { + errors = new ErrorRecord[]{parseError}; + return false; + } - if (!parsedContentSuccessfully) + // populate PSScriptFileInfo object by first creating instances for the property objects + // i.e (PSScriptMetadata, PSScriptHelp, PSScriptRequires, PSScriptContents) + if (!TryPopulateScriptClassesWithParsedContent( + psScriptInfoCommentContent: psScriptInfoCommentContent, + helpInfoCommentContent: helpInfoCommentContent, + requiresCommentContent: requiresCommentContent, + remainingFileContent: remainingFileContent, + currentMetadata: out PSScriptMetadata currentMetadata, + currentHelpInfo: out PSScriptHelp currentHelpInfo, + currentRequiresComment: out PSScriptRequires currentRequiresComment, + currentEndOfFileContents: out PSScriptContents currentEndOfFileContents, + errors: out errors, + out verboseMsgs)) { - errors = errorsList.ToArray(); - return parsedContentSuccessfully; + return false; } + // create PSScriptFileInfo instance with script metadata class instances (PSScriptMetadata, PSScriptHelp, PSScriptRequires, PSScriptContents) try { parsedScript = new PSScriptFileInfo( @@ -277,12 +340,12 @@ internal static bool TryParseScriptFile( var message = String.Format("PSScriptFileInfo object could not be created from passed in file due to {0}", e.Message); var ex = new ArgumentException(message); var PSScriptFileInfoObjectNotCreatedFromFileError = new ErrorRecord(ex, "PSScriptFileInfoObjectNotCreatedFromFileError", ErrorCategory.ParserError, null); - errorsList.Add(PSScriptFileInfoObjectNotCreatedFromFileError); - parsedContentSuccessfully = false; + errors = new ErrorRecord[]{PSScriptFileInfoObjectNotCreatedFromFileError}; + return false; } errors = errorsList.ToArray(); - return parsedContentSuccessfully; + return true; } /// diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 4ed3e50cd..2f86cecbe 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -58,7 +58,7 @@ protected override void EndProcessing() ThrowTerminatingError(FileDoesNotExistError); } - bool isValidScript = PSScriptFileInfo.TryParseScriptFile( + bool isValidScript = PSScriptFileInfo.TryTestPSScriptFile( scriptFileInfoPath: resolvedFilePath, parsedScript: out PSScriptFileInfo parsedScriptInfo, errors: out ErrorRecord[] errors, diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index f49dcd6c4..ffc384db6 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -227,7 +227,7 @@ protected override void EndProcessing() } } - if (!PSScriptFileInfo.TryParseScriptFile( + if (!PSScriptFileInfo.TryTestPSScriptFile( scriptFileInfoPath: resolvedFilePath, parsedScript: out PSScriptFileInfo parsedScriptInfo, errors: out ErrorRecord[] errors, From cf85b83c8603166cb630f63ed22c250df503b91a Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 12:43:49 -0400 Subject: [PATCH 10/29] add newlines to end of files --- src/code/PSScriptContents.cs | 2 +- src/code/PSScriptHelp.cs | 2 +- src/code/PSScriptMetadata.cs | 2 +- src/code/PSScriptRequires.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/code/PSScriptContents.cs b/src/code/PSScriptContents.cs index 3aa121d64..5ae416433 100644 --- a/src/code/PSScriptContents.cs +++ b/src/code/PSScriptContents.cs @@ -107,4 +107,4 @@ private void RemoveSignatureString() } #endregion } -} \ No newline at end of file +} diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs index 5fdb7484b..4f21fac76 100644 --- a/src/code/PSScriptHelp.cs +++ b/src/code/PSScriptHelp.cs @@ -311,4 +311,4 @@ private bool StringContainsComment(string stringToValidate) #endregion } -} \ No newline at end of file +} diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs index 633cf3066..3ae9ed5d6 100644 --- a/src/code/PSScriptMetadata.cs +++ b/src/code/PSScriptMetadata.cs @@ -447,4 +447,4 @@ internal bool UpdateContent( #endregion } -} \ No newline at end of file +} diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs index 2c437e5ed..d7b84986d 100644 --- a/src/code/PSScriptRequires.cs +++ b/src/code/PSScriptRequires.cs @@ -141,4 +141,4 @@ internal void UpdateContent(ModuleSpecification[] requiredModules) #endregion } -} \ No newline at end of file +} From 63a140f1d6e53bf77cb1de05596ee58ef8fa9b9a Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:41:22 -0400 Subject: [PATCH 11/29] Update src/code/PSScriptContents.cs Co-authored-by: Paul Higinbotham --- src/code/PSScriptContents.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSScriptContents.cs b/src/code/PSScriptContents.cs index 5ae416433..e77ae0649 100644 --- a/src/code/PSScriptContents.cs +++ b/src/code/PSScriptContents.cs @@ -69,7 +69,7 @@ internal void ParseContent(string[] commentLines) } /// - /// This function would be called by PSScriptFileInfo.TryCreateScriptFileInfoString(), + /// This function is called by PSScriptFileInfo.TryCreateScriptFileInfoString(), /// by the New-PSScriptFileInfo cmdlet (in which case EndOfFileContents is an empty string so there's no signature that'll get removed) /// or by Update-PSScriptFileInfo cmdlet (in which case EndOfFileContents may not be empty and may contain a signature. /// The Update cmdlet checks for -RemoveSignature before control reaches this method). From b9b5177003867325464f90fde97a1884634689bd Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:43:09 -0400 Subject: [PATCH 12/29] Update src/code/PSScriptFileInfo.cs Co-authored-by: Paul Higinbotham --- src/code/PSScriptFileInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index a2d049cf8..ec9576bf7 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -115,7 +115,7 @@ internal static bool TryParseScriptFileContents( string[] fileContents = File.ReadAllLines(scriptFileInfoPath); - bool reachedPSSCriptInfoCommentEnd = false; + bool reachedPSScriptInfoCommentEnd = false; bool reachedHelpInfoCommentEnd = false; int i = 0; From 59698a731ba82b265a0441309b39037a2d6a33bd Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:43:42 -0400 Subject: [PATCH 13/29] Update src/code/PSScriptFileInfo.cs Co-authored-by: Paul Higinbotham --- src/code/PSScriptFileInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index ec9576bf7..472eb1dca 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -296,7 +296,7 @@ internal static bool TryTestPSScriptFile( List requiresCommentContent = new List(); string[] remainingFileContent = new string[]{}; - // parse .ps1 contents out of file into list objects + // Parse .ps1 contents out of file into list objects if (!TryParseScriptFileContents( scriptFileInfoPath: scriptFileInfoPath, psScriptInfoCommentContent: ref psScriptInfoCommentContent, From 6a2b2fa76a3b3f9927824638b5a2d96b72226055 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:44:29 -0400 Subject: [PATCH 14/29] Update src/code/PSScriptFileInfo.cs Co-authored-by: Paul Higinbotham --- src/code/PSScriptFileInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 472eb1dca..08318a5ea 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -309,7 +309,7 @@ internal static bool TryTestPSScriptFile( return false; } - // populate PSScriptFileInfo object by first creating instances for the property objects + // Populate PSScriptFileInfo object by first creating instances for the property objects // i.e (PSScriptMetadata, PSScriptHelp, PSScriptRequires, PSScriptContents) if (!TryPopulateScriptClassesWithParsedContent( psScriptInfoCommentContent: psScriptInfoCommentContent, From 3167cee1bf3d0d0bd16360425528299a82d8bfcd Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:44:47 -0400 Subject: [PATCH 15/29] Update src/code/PSScriptFileInfo.cs Co-authored-by: Paul Higinbotham --- src/code/PSScriptFileInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 08318a5ea..26cc21c82 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -326,7 +326,7 @@ internal static bool TryTestPSScriptFile( return false; } - // create PSScriptFileInfo instance with script metadata class instances (PSScriptMetadata, PSScriptHelp, PSScriptRequires, PSScriptContents) + // Create PSScriptFileInfo instance with script metadata class instances (PSScriptMetadata, PSScriptHelp, PSScriptRequires, PSScriptContents) try { parsedScript = new PSScriptFileInfo( From 970aae4cd1b60d2c1f2186d76a1afbe21a631cc9 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:45:01 -0400 Subject: [PATCH 16/29] Update src/code/Utils.cs Co-authored-by: Paul Higinbotham --- src/code/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 4e1ae6692..6f4841eed 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -938,7 +938,7 @@ public static bool TryCreateModuleSpecification( foreach(Hashtable moduleSpec in moduleSpecHashtables) { - // ModuleSpecification(string) constructor for creating a ModuleSpecification when only ModuleName is provided + // ModuleSpecification(string) constructor for creating a ModuleSpecification when only ModuleName is provided. if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) { var exMessage = $"RequiredModules Hashtable entry {moduleSpec.ToString()} is missing a key 'ModuleName' and associated value, which is required for each module specification entry"; From f0e51c462b860ebff3f9e1b872ee550365e60930 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:45:13 -0400 Subject: [PATCH 17/29] Update src/code/Utils.cs Co-authored-by: Paul Higinbotham --- src/code/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 6f4841eed..965ebd46b 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -974,7 +974,7 @@ public static bool TryCreateModuleSpecification( } else { - // ModuleSpecification(Hashtable) constructor for when ModuleName + {Required,Maximum,Module}Version value is also provided + // ModuleSpecification(Hashtable) constructor for when ModuleName + {Required,Maximum,Module}Version value is also provided. string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaximumVersion"] : String.Empty; string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; string moduleSpecRequiredVersion = moduleSpec.ContainsKey("RequiredVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; From aff3cb340b26ee2d2912654c14b671f92f27f537 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:45:23 -0400 Subject: [PATCH 18/29] Update src/code/Utils.cs Co-authored-by: Paul Higinbotham --- src/code/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 965ebd46b..775a20d9b 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -955,7 +955,7 @@ public static bool TryCreateModuleSpecification( if (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion")) { // pass to ModuleSpecification(string) constructor - // this constructor method would only throw for a null/empty string, which we've already validated against above + // This constructor method would only throw for a null/empty string, which we've already validated against above. currentModuleSpec = new ModuleSpecification(moduleSpecName); if (currentModuleSpec != null) From 2b42ed50e674896e658f6c0bbe10336c279e8043 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:45:31 -0400 Subject: [PATCH 19/29] Update src/code/Utils.cs Co-authored-by: Paul Higinbotham --- src/code/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 775a20d9b..2ab8f55e7 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -954,7 +954,7 @@ public static bool TryCreateModuleSpecification( ModuleSpecification currentModuleSpec = null; if (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion")) { - // pass to ModuleSpecification(string) constructor + // Pass to ModuleSpecification(string) constructor. // This constructor method would only throw for a null/empty string, which we've already validated against above. currentModuleSpec = new ModuleSpecification(moduleSpecName); From 2002fbdecfbb65865c5a4e3b4e20a19399e476e9 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:45:40 -0400 Subject: [PATCH 20/29] Update src/code/Utils.cs Co-authored-by: Paul Higinbotham --- src/code/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 2ab8f55e7..287d4956e 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -949,7 +949,7 @@ public static bool TryCreateModuleSpecification( continue; } - // at this point it must contain ModuleName key. + // At this point it must contain ModuleName key. string moduleSpecName = (string) moduleSpec["ModuleName"]; ModuleSpecification currentModuleSpec = null; if (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion")) From 759b657d3b0430b6827d476ca11f67cc4bace117 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:46:24 -0400 Subject: [PATCH 21/29] Update src/code/PSScriptFileInfo.cs Co-authored-by: Paul Higinbotham --- src/code/PSScriptFileInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 26cc21c82..b1e2cb231 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -351,7 +351,7 @@ internal static bool TryTestPSScriptFile( /// /// Updates .ps1 file. /// Caller must check that the file to update doesn't have a signature or if it does permission to remove signature has been granted - /// as this method will remove original signature, as updating would have invalidated it + /// as this method will remove original signature, as updating would have invalidated it. /// internal static bool TryUpdateScriptFileContents( PSScriptFileInfo scriptInfo, From 37a41f5ee846d451dd1e5addfdb8de5aa7b21d2a Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:46:49 -0400 Subject: [PATCH 22/29] Update src/code/PSScriptFileInfo.cs Co-authored-by: Paul Higinbotham --- src/code/PSScriptFileInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index b1e2cb231..b91a6f42a 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -458,7 +458,7 @@ out ErrorRecord[] errors bool fileContentsSuccessfullyCreated = true; - // step 1: validate object properties for required script properties + // Step 1: validate object properties for required script properties. if (!ScriptMetadataComment.ValidateContent(out ErrorRecord[] metadataValidationErrors)) { errorsList.AddRange(metadataValidationErrors); From 815579e81bd63c6e19118c731ed7895f3c1b5892 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:47:18 -0400 Subject: [PATCH 23/29] Update src/code/PSScriptFileInfo.cs Co-authored-by: Paul Higinbotham --- src/code/PSScriptFileInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index b91a6f42a..17270f25e 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -477,7 +477,7 @@ out ErrorRecord[] errors return fileContentsSuccessfullyCreated; } - // step 2: create string that will be used to write later + // Step 2: create string that will be used to write later. psScriptFileString = ScriptMetadataComment.EmitContent(); string psRequiresCommentBlock = ScriptRequiresComment.EmitContent(); From ed4f2272195dd3914bb1647c990b8fa223e6f57f Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 18 Jul 2022 15:47:35 -0400 Subject: [PATCH 24/29] Update src/code/UpdatePSScriptFileInfo.cs Co-authored-by: Paul Higinbotham --- src/code/UpdatePSScriptFileInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index ffc384db6..27ba9b1ea 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -237,7 +237,7 @@ protected override void EndProcessing() { WriteVerbose(msg); - // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. + // Also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. WriteWarning(msg); } From 4d240e493403a3e3e24963d8780473355f63d499 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 19 Jul 2022 16:42:15 -0400 Subject: [PATCH 25/29] preserve newlines by using File.WriteAllLines, and address other code review feedback --- src/code/NewPSScriptFileInfo.cs | 4 +- src/code/PSScriptContents.cs | 45 ++++++++++------- src/code/PSScriptFileInfo.cs | 71 +++++++++++---------------- src/code/PSScriptHelp.cs | 58 +++++++++++++--------- src/code/PSScriptMetadata.cs | 58 ++++++++++++---------- src/code/PSScriptRequires.cs | 28 +++++------ src/code/TestPSScriptFileInfo.cs | 7 +-- src/code/UpdatePSScriptFileInfo.cs | 25 +++++----- test/NewPSScriptFileInfo.Tests.ps1 | 4 +- test/UpdatePSScriptFileInfo.Tests.ps1 | 10 ++-- 10 files changed, 159 insertions(+), 151 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 830fad49e..baa3fa9bf 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -239,7 +239,7 @@ protected override void EndProcessing() description: Description); if (!scriptInfo.TryCreateScriptFileInfoString( - psScriptFileString: out string psScriptFileContents, + psScriptFileContents: out string[] psScriptFileContents, errors: out ErrorRecord[] errors)) { foreach (ErrorRecord err in errors) @@ -250,7 +250,7 @@ protected override void EndProcessing() return; } - File.WriteAllText(resolvedFilePath, psScriptFileContents); + File.WriteAllLines(resolvedFilePath, psScriptFileContents); } #endregion diff --git a/src/code/PSScriptContents.cs b/src/code/PSScriptContents.cs index e77ae0649..225e84c07 100644 --- a/src/code/PSScriptContents.cs +++ b/src/code/PSScriptContents.cs @@ -2,17 +2,6 @@ // Licensed under the MIT License. using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Management.Automation; -using System.Management.Automation.Language; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; -using System.Linq; -using System.Collections.ObjectModel; -using Microsoft.PowerShell.Commands; -using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -26,7 +15,7 @@ public sealed class PSScriptContents /// /// End of file contents for the .ps1 file. /// - public string EndOfFileContents { get; private set; } = String.Empty; + public string[] EndOfFileContents { get; private set; } = Utils.EmptyStrArray; /// /// End of file contents for the .ps1 file. @@ -38,17 +27,25 @@ public sealed class PSScriptContents #region Private Members private const string signatureStartString = "# SIG # Begin signature block"; + private int _signatureStartIndex = -1; #endregion #region Constructor - public PSScriptContents(string endOfFileContents) + /// + /// This constructor takes end of file contents as a string and checks if it has a signature. + /// + public PSScriptContents(string[] endOfFileContents) { this.EndOfFileContents = endOfFileContents; this.ContainsSignature = CheckForSignature(); } + /// + /// This constructor creates a PSScriptContents instance with default values for its properties. + /// The calling method, like PSScriptContents.ParseContent() could then populate the properties. + /// internal PSScriptContents() {} #endregion @@ -63,7 +60,7 @@ internal void ParseContent(string[] commentLines) { if (commentLines.Length != 0) { - EndOfFileContents = String.Join("", commentLines); + EndOfFileContents = commentLines; ContainsSignature = CheckForSignature(); } } @@ -72,9 +69,9 @@ internal void ParseContent(string[] commentLines) /// This function is called by PSScriptFileInfo.TryCreateScriptFileInfoString(), /// by the New-PSScriptFileInfo cmdlet (in which case EndOfFileContents is an empty string so there's no signature that'll get removed) /// or by Update-PSScriptFileInfo cmdlet (in which case EndOfFileContents may not be empty and may contain a signature. - /// The Update cmdlet checks for -RemoveSignature before control reaches this method). + /// When emitting contents, any file signature is always removed because it is invalidated when the content is updated. /// - internal string EmitContent() + internal string[] EmitContent() { RemoveSignatureString(); return EndOfFileContents; @@ -89,7 +86,15 @@ internal string EmitContent() /// private bool CheckForSignature() { - return (EndOfFileContents.Contains(signatureStartString)); + for (int i = 0; i < EndOfFileContents.Length; i++) + { + if (String.Equals(EndOfFileContents[i], signatureStartString, StringComparison.InvariantCultureIgnoreCase)) + { + _signatureStartIndex = i; + } + } + + return _signatureStartIndex != -1; } /// @@ -100,8 +105,10 @@ private void RemoveSignatureString() { if (ContainsSignature) { - int signatureStartIndex = EndOfFileContents.IndexOf(signatureStartString); - EndOfFileContents = EndOfFileContents.Substring(0, signatureStartIndex); + string[] newEndOfFileContents = new string[EndOfFileContents.Length - _signatureStartIndex]; + Array.Copy(EndOfFileContents, newEndOfFileContents, _signatureStartIndex); + EndOfFileContents = newEndOfFileContents; + ContainsSignature = false; } } diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 17270f25e..d27737cb9 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -2,17 +2,11 @@ // Licensed under the MIT License. using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Management.Automation; -using System.Management.Automation.Language; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; using System.Linq; -using System.Collections.ObjectModel; using Microsoft.PowerShell.Commands; -using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -34,6 +28,10 @@ public sealed class PSScriptFileInfo #region Constructor + /// + /// This constructor takes metadata values that could have been passed in by the calling cmdlet + /// and uses those to create associated script class properties (PSScriptMetadata, PSScriptHelp, PSScriptRequires, PSScriptContents) + /// public PSScriptFileInfo( string version, Guid guid, @@ -70,7 +68,7 @@ public PSScriptFileInfo( PSScriptHelp scriptHelpComment = new PSScriptHelp(description); PSScriptRequires scriptRequiresComment = new PSScriptRequires(requiredModules); - PSScriptContents scriptRemainingContent = new PSScriptContents(String.Empty); + PSScriptContents scriptRemainingContent = new PSScriptContents(Utils.EmptyStrArray); this.ScriptMetadataComment = scriptMetadataComment; this.ScriptHelpComment = scriptHelpComment; @@ -78,6 +76,9 @@ public PSScriptFileInfo( this.ScriptContent = scriptRemainingContent; } + /// + /// This constructor takes script class properties' values that could have been passed in by the calling internal methods. + /// public PSScriptFileInfo( PSScriptMetadata scriptMetadataComment, PSScriptHelp scriptHelpComment, @@ -111,7 +112,7 @@ internal static bool TryParseScriptFileContents( psScriptInfoCommentContent = new List(); helpInfoCommentContent = new List(); requiresCommentContent = new List(); - remainingFileContent = new string[]{}; + remainingFileContent = Utils.EmptyStrArray; string[] fileContents = File.ReadAllLines(scriptFileInfoPath); @@ -133,7 +134,7 @@ internal static bool TryParseScriptFileContents( string blockLine = fileContents[j]; if (blockLine.StartsWith("#>")) { - reachedPSSCriptInfoCommentEnd = true; + reachedPSScriptInfoCommentEnd = true; i = j + 1; break; } @@ -142,7 +143,7 @@ internal static bool TryParseScriptFileContents( j++; } - if (!reachedPSSCriptInfoCommentEnd) + if (!reachedPSScriptInfoCommentEnd) { var message = String.Format("Could not parse '{0}' as a PowerShell script file due to missing the closing '#>' for <#PSScriptInfo comment block", scriptFileInfoPath); var ex = new InvalidOperationException(message); @@ -152,7 +153,7 @@ internal static bool TryParseScriptFileContents( } else if (line.StartsWith("<#")) { - // we assume the next comment block should be the help comment block (containing description) + // The next comment block must be the help comment block (containing description) // keep grabbing lines until we get to closing #> int j = i + 1; while (j < fileContents.Length) @@ -198,6 +199,7 @@ internal static bool TryParseScriptFileContents( { // from this line to fileContents.Length is the endOfFileContents, if any remainingFileContent = new string[fileContents.Length - endOfFileContentsStartIndex]; + Array.Copy(fileContents, endOfFileContentsStartIndex, remainingFileContent, 0, (fileContents.Length - endOfFileContentsStartIndex)); } @@ -284,8 +286,8 @@ internal static bool TryTestPSScriptFile( string scriptFileInfoPath, out PSScriptFileInfo parsedScript, out ErrorRecord[] errors, - out string[] verboseMsgs // this is for Uri errors, which aren't required by script but we check if those in the script aren't valid Uri's. - ) + // this is for Uri errors, which aren't required by script but we check if those in the script aren't valid Uri's. + out string[] verboseMsgs) { verboseMsgs = new string[]{}; List errorsList = new List(); @@ -355,7 +357,7 @@ internal static bool TryTestPSScriptFile( /// internal static bool TryUpdateScriptFileContents( PSScriptFileInfo scriptInfo, - out string updatedPSScriptFileContents, + out string[] updatedPSScriptFileContents, out ErrorRecord[] errors, string version, Guid guid, @@ -374,18 +376,13 @@ internal static bool TryUpdateScriptFileContents( string privateData, string description) { - updatedPSScriptFileContents = String.Empty; + updatedPSScriptFileContents = Utils.EmptyStrArray; List errorsList = new List(); bool successfullyUpdated = true; if (scriptInfo == null) { - var message = String.Format("Could not update .ps1 file as PSScriptFileInfo object created for it was null"); - var ex = new ArgumentException(message); - var nullPSScriptFileInfoError = new ErrorRecord(ex, "NullPSScriptFileInfoError", ErrorCategory.ParserError, null); - errors = new ErrorRecord[]{nullPSScriptFileInfoError}; - - return false; + throw new ArgumentNullException(nameof(scriptInfo)); } if (!scriptInfo.ScriptMetadataComment.UpdateContent( @@ -429,7 +426,7 @@ internal static bool TryUpdateScriptFileContents( // create string contents for .ps1 file if (!scriptInfo.TryCreateScriptFileInfoString( - psScriptFileString: out updatedPSScriptFileContents, + psScriptFileContents: out updatedPSScriptFileContents, errors: out ErrorRecord[] createUpdatedFileContentErrors)) { errorsList.AddRange(createUpdatedFileContentErrors); @@ -448,11 +445,12 @@ internal static bool TryUpdateScriptFileContents( /// Creates .ps1 file content string representation for the PSScriptFileInfo object this called upon, which is used by the caller to write the .ps1 file. /// internal bool TryCreateScriptFileInfoString( - out string psScriptFileString, + out string[] psScriptFileContents, out ErrorRecord[] errors ) { - psScriptFileString = String.Empty; + psScriptFileContents = Utils.EmptyStrArray; + List fileContentsList = new List(); errors = new ErrorRecord[]{}; List errorsList = new List(); @@ -477,26 +475,15 @@ out ErrorRecord[] errors return fileContentsSuccessfullyCreated; } - // Step 2: create string that will be used to write later. - psScriptFileString = ScriptMetadataComment.EmitContent(); - - string psRequiresCommentBlock = ScriptRequiresComment.EmitContent(); - if (!String.IsNullOrEmpty(psRequiresCommentBlock)) - { - psScriptFileString += "\n"; - psScriptFileString += psRequiresCommentBlock; - } - - psScriptFileString += "\n"; // need a newline after last #> and before <# for script comment block, TODO: try removing - // or else not recongnized as a valid comment help info block when parsing the created ps1 later - psScriptFileString += "\n" + ScriptHelpComment.EmitContent(); + // Step 2: create string [] that will be used to write to file later + fileContentsList.AddRange(ScriptMetadataComment.EmitContent()); - string psEndOfFileContent = ScriptContent.EmitContent(); - if (!String.IsNullOrEmpty(psEndOfFileContent)) - { - psScriptFileString += "\n" + psEndOfFileContent; - } + // string psRequiresCommentBlock = ScriptRequiresComment.EmitContent(); + fileContentsList.AddRange(ScriptRequiresComment.EmitContent()); + fileContentsList.AddRange(ScriptHelpComment.EmitContent()); + fileContentsList.AddRange(ScriptContent.EmitContent()); + psScriptFileContents = fileContentsList.ToArray(); return fileContentsSuccessfullyCreated; } diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs index 4f21fac76..f799600b9 100644 --- a/src/code/PSScriptHelp.cs +++ b/src/code/PSScriptHelp.cs @@ -4,15 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; using System.Management.Automation; -using System.Management.Automation.Language; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; -using System.Linq; -using System.Collections.ObjectModel; -using Microsoft.PowerShell.Commands; -using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -77,11 +69,18 @@ public sealed class PSScriptHelp #region Constructor + /// + /// This constructor takes a value for description and creates a new PSScriptHelp instance. + /// public PSScriptHelp (string description) { this.Description = description; } + /// + /// This constructor takes values for description as well as other properties and creates a new PSScriptHelp instance. + /// Currently, the New-PSScriptFileInfo and Update-PSScriptFileInfo cmdlets don't support the user providing these values. + /// public PSScriptHelp ( string description, string synopsis, @@ -106,6 +105,10 @@ public PSScriptHelp ( this.Functionality = functionality; } + /// + /// This constructor is called by internal cmdlet methods and creates a PSScriptHelp with default values + /// for the parameters. Calling a method like PSScriptHelp.ParseConentIntoObj() would then populate those properties. + /// internal PSScriptHelp() {} #endregion @@ -204,64 +207,71 @@ internal bool ValidateContent(out ErrorRecord error) /// /// Emits string representation of 'HelpInfo <# ... #>' comment and its metadata contents. /// - internal string EmitContent() + internal string[] EmitContent() { - string psHelpInfo; List psHelpInfoLines = new List(); psHelpInfoLines.Add("<#\n"); - psHelpInfoLines.Add(String.Format(".DESCRIPTION\n{0}", Description)); + psHelpInfoLines.Add($".DESCRIPTION"); + psHelpInfoLines.Add($"{Description}{Environment.NewLine}"); if (!String.IsNullOrEmpty(Synopsis)) { - psHelpInfoLines.Add(String.Format(".SYNOPSIS\n{0}", Synopsis)); + psHelpInfoLines.Add($".SYNOPSIS"); + psHelpInfoLines.Add($"{Synopsis}{Environment.NewLine}"); } foreach (string currentExample in Example) { - psHelpInfoLines.Add(String.Format(".EXAMPLE\n{0}", currentExample)); + psHelpInfoLines.Add($".EXAMPLE"); + psHelpInfoLines.Add($"{currentExample}{Environment.NewLine}"); } foreach (string input in Inputs) { - psHelpInfoLines.Add(String.Format(".INPUTS\n{0}", input)); + psHelpInfoLines.Add($".INPUTS"); + psHelpInfoLines.Add($"{input}{Environment.NewLine}"); } foreach (string output in Outputs) { - psHelpInfoLines.Add(String.Format(".OUTPUTS\n{0}", output)); + psHelpInfoLines.Add($".OUTPUTS"); + psHelpInfoLines.Add($"{output}{Environment.NewLine}"); } if (Notes.Length > 0) { - psHelpInfoLines.Add(String.Format(".NOTES\n{0}", String.Join("\n", Notes))); + psHelpInfoLines.Add($".NOTES"); + psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Notes)}{Environment.NewLine}"); } foreach (string link in Links) { - psHelpInfoLines.Add(String.Format(".LINK\n{0}", link)); + psHelpInfoLines.Add($".LINK"); + psHelpInfoLines.Add($"{link}{Environment.NewLine}"); } if (Component.Length > 0) { - psHelpInfoLines.Add(String.Format(".COMPONENT\n{0}", String.Join("\n", Component))); + psHelpInfoLines.Add($".COMPONENT"); + psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Component)}{Environment.NewLine}"); } if (Role.Length > 0) { - psHelpInfoLines.Add(String.Format(".ROLE\n{0}", String.Join("\n", Role))); + psHelpInfoLines.Add($".ROLE"); + psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Role)}{Environment.NewLine}"); } if (Functionality.Length > 0) { - psHelpInfoLines.Add(String.Format(".FUNCTIONALITY\n{0}", String.Join("\n", Functionality))); + psHelpInfoLines.Add($".FUNCTIONALITY"); + psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Functionality)}{Environment.NewLine}"); } - psHelpInfo = String.Join("\n", psHelpInfoLines); - psHelpInfo = psHelpInfo.TrimEnd('\n'); - psHelpInfo += "\n\n#>"; + psHelpInfoLines.Add("#>"); - return psHelpInfo; + return psHelpInfoLines.ToArray(); } /// diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs index 3ae9ed5d6..be44b9d04 100644 --- a/src/code/PSScriptMetadata.cs +++ b/src/code/PSScriptMetadata.cs @@ -1,18 +1,10 @@ -using System.Net.Http.Headers; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; -using System.IO; using System.Management.Automation; -using System.Management.Automation.Language; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; -using System.Linq; -using System.Collections.ObjectModel; -using Microsoft.PowerShell.Commands; using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses @@ -98,6 +90,9 @@ public sealed class PSScriptMetadata #region Constructor + /// + /// This constructor takes metadata properties and creates PSScriptMetadata instance. + /// public PSScriptMetadata( string version, Guid guid, @@ -135,6 +130,10 @@ public PSScriptMetadata( PrivateData = privateData; } + /// + /// This constructor is called by internal cmdlet methods and creates a PSScriptFileInfo with default values + /// for the parameters. Calling a method like PSScriptMetadata.ParseConentIntoObj() would then populate those properties. + /// internal PSScriptMetadata() {} #endregion @@ -300,7 +299,7 @@ internal bool ValidateContent(out ErrorRecord[] errors) /// /// Emits string representation of '<#PSScriptInfo ... #>' comment and its metadata contents. /// - internal string EmitContent() + internal string[] EmitContent() { /** PSScriptInfo comment will be in following format: @@ -328,26 +327,35 @@ Feature 5 #> */ + string liceseUriString = LicenseUri == null ? String.Empty : LicenseUri.ToString(); + string projectUriString = ProjectUri == null ? String.Empty : ProjectUri.ToString(); + string iconUriString = IconUri == null ? String.Empty : IconUri.ToString(); + + string tagsString = String.Join(" ", Tags); + string externalModuleDependenciesString = String.Join(" ", ExternalModuleDependencies); + string requiredScriptsString = String.Join(" ", RequiredScripts); + string externalScriptDependenciesString = String.Join(" ", ExternalScriptDependencies); + List psScriptInfoLines = new List(); - psScriptInfoLines.Add("<#PSScriptInfo"); - psScriptInfoLines.Add(String.Format(".VERSION {0}", Version.ToString())); - psScriptInfoLines.Add(String.Format(".GUID {0}", Guid.ToString())); - psScriptInfoLines.Add(String.Format(".AUTHOR {0}", Author)); - psScriptInfoLines.Add(String.Format(".COMPANYNAME {0}", CompanyName)); - psScriptInfoLines.Add(String.Format(".COPYRIGHT {0}", Copyright)); - psScriptInfoLines.Add(String.Format(".TAGS {0}", String.Join(" ", Tags))); - psScriptInfoLines.Add(String.Format(".LICENSEURI {0}", LicenseUri == null ? String.Empty : LicenseUri.ToString())); - psScriptInfoLines.Add(String.Format(".PROJECTURI {0}", ProjectUri == null ? String.Empty : ProjectUri.ToString())); - psScriptInfoLines.Add(String.Format(".ICONURI {0}", IconUri == null ? String.Empty : IconUri.ToString())); - psScriptInfoLines.Add(String.Format(".EXTERNALMODULEDEPENDENCIES {0}", String.Join(" ", ExternalModuleDependencies))); - psScriptInfoLines.Add(String.Format(".REQUIREDSCRIPTS {0}", String.Join(" ", RequiredScripts))); - psScriptInfoLines.Add(String.Format(".EXTERNALSCRIPTDEPENDENCIES {0}", String.Join(" ", ExternalScriptDependencies))); - psScriptInfoLines.Add(String.Format(".RELEASENOTES\n{0}", ReleaseNotes)); - psScriptInfoLines.Add(String.Format(".PRIVATEDATA\n{0}", PrivateData)); + psScriptInfoLines.Add($"<#PSScriptInfo{Environment.NewLine}"); + psScriptInfoLines.Add($".VERSION {Version.ToString()}{Environment.NewLine}"); + psScriptInfoLines.Add($".GUID {Guid.ToString()}{Environment.NewLine}"); + psScriptInfoLines.Add($".AUTHOR {Author}{Environment.NewLine}"); + psScriptInfoLines.Add($".COMPANYNAME {CompanyName}{Environment.NewLine}"); + psScriptInfoLines.Add($".COPYRIGHT {Copyright}{Environment.NewLine}"); + psScriptInfoLines.Add($".TAGS {tagsString}{Environment.NewLine}"); + psScriptInfoLines.Add($".LICENSEURI {liceseUriString}{Environment.NewLine}"); + psScriptInfoLines.Add($".PROJECTURI {projectUriString}{Environment.NewLine}"); + psScriptInfoLines.Add($".ICONURI {iconUriString}{Environment.NewLine}"); + psScriptInfoLines.Add($".EXTERNALMODULEDEPENDENCIES {externalModuleDependenciesString}{Environment.NewLine}"); + psScriptInfoLines.Add($".REQUIREDSCRIPTS {requiredScriptsString}{Environment.NewLine}"); + psScriptInfoLines.Add($".EXTERNALSCRIPTDEPENDENCIES {externalScriptDependenciesString}{Environment.NewLine}"); + psScriptInfoLines.Add($".RELEASENOTES{Environment.NewLine}{ReleaseNotes}{Environment.NewLine}"); + psScriptInfoLines.Add($".PRIVATEDATA{Environment.NewLine}{PrivateData}{Environment.NewLine}"); psScriptInfoLines.Add("#>"); - return String.Join("\n\n", psScriptInfoLines); + return psScriptInfoLines.ToArray(); } /// diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs index d7b84986d..dabf34fde 100644 --- a/src/code/PSScriptRequires.cs +++ b/src/code/PSScriptRequires.cs @@ -2,17 +2,12 @@ // Licensed under the MIT License. using System; -using System.Collections; using System.Collections.Generic; -using System.IO; using System.Management.Automation; using System.Management.Automation.Language; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; using System.Linq; using System.Collections.ObjectModel; using Microsoft.PowerShell.Commands; -using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -33,11 +28,18 @@ public sealed class PSScriptRequires #region Constructor + /// + /// This constructor creates a new PSScriptRequires instance with specified required modules. + /// public PSScriptRequires(ModuleSpecification[] requiredModules) { this.RequiredModules = requiredModules ?? new ModuleSpecification[]{}; } + /// + /// This constructor is called by internal cmdlet methods and creates a PSScriptHelp with default values + /// for the parameters. Calling a method like PSScriptRequires.ParseConentIntoObj() would then populate those properties. + /// internal PSScriptRequires() {} #endregion @@ -110,22 +112,21 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error /// /// Emits string representation of '#Requires ...' comment(s). /// - internal string EmitContent() - { + internal string[] EmitContent() + { + List psRequiresLines = new List(); if (RequiredModules.Length > 0) { - List psRequiresLines = new List(); - psRequiresLines.Add("\n"); + psRequiresLines.Add(String.Empty); foreach (ModuleSpecification moduleSpec in RequiredModules) { psRequiresLines.Add(String.Format("#Requires -Module {0}", moduleSpec.ToString())); } - - psRequiresLines.Add("\n"); - return String.Join("\n", psRequiresLines); + + psRequiresLines.Add(String.Empty); } - return String.Empty; + return psRequiresLines.ToArray(); } /// @@ -138,7 +139,6 @@ internal void UpdateContent(ModuleSpecification[] requiredModules) } } - #endregion } } diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 2f86cecbe..931d04c7c 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -60,7 +60,7 @@ protected override void EndProcessing() bool isValidScript = PSScriptFileInfo.TryTestPSScriptFile( scriptFileInfoPath: resolvedFilePath, - parsedScript: out PSScriptFileInfo parsedScriptInfo, + parsedScript: out PSScriptFileInfo _, errors: out ErrorRecord[] errors, out string[] verboseMsgs); @@ -68,16 +68,13 @@ protected override void EndProcessing() { foreach (ErrorRecord error in errors) { - WriteWarning("The .ps1 script file passed in was not valid due to: " + error.Exception.Message); + WriteVerbose("The .ps1 script file passed in was not valid due to: " + error.Exception.Message); } } foreach (string msg in verboseMsgs) { WriteVerbose(msg); - - // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. - WriteWarning(msg); } WriteObject(isValidScript); diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 27ba9b1ea..7fac81056 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -146,12 +146,6 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet #endregion - #region Private Members - - private const string signatureStartString = "# SIG # Begin signature block"; - - #endregion - #region Methods protected override void EndProcessing() @@ -236,12 +230,8 @@ protected override void EndProcessing() foreach (string msg in verboseMsgs) { WriteVerbose(msg); - - // Also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. - WriteWarning(msg); } - WriteWarning("The .ps1 script file passed in was not valid due to the following error(s) listed below"); foreach (ErrorRecord error in errors) { WriteError(error); @@ -250,9 +240,9 @@ protected override void EndProcessing() return; } + bool signatureRemoved = false; if (parsedScriptInfo.ScriptContent.ContainsSignature) { - WriteWarning("This script contains a signature and cannot be updated without invalidating the current script signature"); if (!RemoveSignature) { var exMessage = "Cannot update the script file because the file contains a signature block and updating will invalidate the signature. Use -RemoveSignature to remove the signature block, and then re-sign the file after it is updated."; @@ -260,11 +250,13 @@ protected override void EndProcessing() var ScriptToBeUpdatedContainsSignatureError = new ErrorRecord(ex, "ScriptToBeUpdatedContainsSignature", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(ScriptToBeUpdatedContainsSignatureError); } + + signatureRemoved = true; } if (!PSScriptFileInfo.TryUpdateScriptFileContents( scriptInfo: parsedScriptInfo, - updatedPSScriptFileContents: out string updatedPSScriptFileContents, + updatedPSScriptFileContents: out string[] updatedPSScriptFileContents, errors: out ErrorRecord[] updateErrors, version: Version, guid: Guid, @@ -297,7 +289,7 @@ protected override void EndProcessing() { tempScriptFilePath = Path.GetTempFileName(); - File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); + File.WriteAllLines(tempScriptFilePath, updatedPSScriptFileContents); File.Copy(tempScriptFilePath, resolvedFilePath, overwrite: true); } catch(Exception e) @@ -314,7 +306,12 @@ protected override void EndProcessing() { File.Delete(tempScriptFilePath); } - } + } + + if (signatureRemoved) + { + WriteWarning("Re-sign this script, as the original signature was removed during update."); + } } #endregion diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index ad77a0270..4b48ccd57 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -81,7 +81,7 @@ Describe "Test New-PSScriptFileInfo" { Test-Path -Path $script:testScriptFilePath | Should -BeTrue $results = Get-Content -Path $script:testScriptFilePath -Raw $results.Contains($description) | Should -BeTrue - $results -like "*.DESCRIPTION`n*$description*" | Should -BeTrue + $results -like "*.DESCRIPTION$script:newline*$description*" | Should -BeTrue } It "Create new .ps1 given CompanyName parameter" { @@ -133,7 +133,7 @@ Describe "Test New-PSScriptFileInfo" { Test-Path -Path $script:testScriptFilePath | Should -BeTrue $results = Get-Content -Path $script:testScriptFilePath -Raw $results.Contains($releaseNotes) | Should -BeTrue - $results -like "*.RELEASENOTES`n*$ReleaseNotes*" | Should -BeTrue + $results -like "*.RELEASENOTES$script:newline*$ReleaseNotes*" | Should -BeTrue } It "Create new .ps1 given Tags parameter" { diff --git a/test/UpdatePSScriptFileInfo.Tests.ps1 b/test/UpdatePSScriptFileInfo.Tests.ps1 index 91d59bbcd..6eac2d1d0 100644 --- a/test/UpdatePSScriptFileInfo.Tests.ps1 +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -14,6 +14,8 @@ Describe "Test Update-PSScriptFileInfo" { # Path to specifically to that invalid test scripts folder $script:testScriptsFolderPath = Join-Path $testFilesFolderPath -ChildPath "testScripts" + + $script:newline = [System.Environment]::NewLine; } BeforeEach { @@ -43,7 +45,7 @@ Describe "Test Update-PSScriptFileInfo" { Test-Path -Path $scriptFilePath | Should -BeTrue $results = Get-Content -Path $scriptFilePath -Raw $results.Contains($newDescription) | Should -BeTrue - $results -like "*.DESCRIPTION`n*$newDescription*" | Should -BeTrue + $results -like "*.DESCRIPTION$script:newline*$newDescription*" | Should -BeTrue Remove-Item -Path $scriptFilePath -Force } @@ -74,7 +76,7 @@ Describe "Test Update-PSScriptFileInfo" { $results.Contains(".VERSION $version") | Should -BeTrue $results.Contains($description) | Should -BeTrue - $results -like "*.DESCRIPTION`n*$description*" | Should -BeTrue + $results -like "*.DESCRIPTION$script:newline*$description*" | Should -BeTrue Remove-Item -Path $scriptFilePath -Force } @@ -126,7 +128,7 @@ Describe "Test Update-PSScriptFileInfo" { Test-Path -Path $script:testScriptFilePath | Should -BeTrue $results = Get-Content -Path $script:testScriptFilePath -Raw $results.Contains($description) | Should -BeTrue - $results -like "*.DESCRIPTION`n*$description*" | Should -BeTrue + $results -like "*.DESCRIPTION$script:newline*$description*" | Should -BeTrue } It "update script file Guid property" { @@ -240,7 +242,7 @@ Describe "Test Update-PSScriptFileInfo" { Test-Path -Path $script:testScriptFilePath | Should -BeTrue $results = Get-Content -Path $script:testScriptFilePath -Raw $results.Contains($releaseNotes) | Should -BeTrue - $results -like "*.RELEASENOTES`n*$releaseNotes*" | Should -BeTrue + $results -like "*.RELEASENOTES$script:newline*$releaseNotes*" | Should -BeTrue } It "update script file RequiredModules property" { From 6be12d1144f62dd0c8813315cacce552989c1248 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 19 Jul 2022 17:07:20 -0400 Subject: [PATCH 26/29] remove this. from Constructor, and remove bracket initializers for string arrays --- src/code/PSScriptContents.cs | 4 ++-- src/code/PSScriptFileInfo.cs | 20 ++++++++++---------- src/code/PSScriptHelp.cs | 22 +++++++++++----------- src/code/PSScriptMetadata.cs | 10 +++++----- src/code/PSScriptRequires.cs | 4 ++-- src/code/Utils.cs | 2 +- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/code/PSScriptContents.cs b/src/code/PSScriptContents.cs index 225e84c07..c04f1dc49 100644 --- a/src/code/PSScriptContents.cs +++ b/src/code/PSScriptContents.cs @@ -38,8 +38,8 @@ public sealed class PSScriptContents /// public PSScriptContents(string[] endOfFileContents) { - this.EndOfFileContents = endOfFileContents; - this.ContainsSignature = CheckForSignature(); + EndOfFileContents = endOfFileContents; + ContainsSignature = CheckForSignature(); } /// diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index d27737cb9..513f365c2 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -70,10 +70,10 @@ public PSScriptFileInfo( PSScriptRequires scriptRequiresComment = new PSScriptRequires(requiredModules); PSScriptContents scriptRemainingContent = new PSScriptContents(Utils.EmptyStrArray); - this.ScriptMetadataComment = scriptMetadataComment; - this.ScriptHelpComment = scriptHelpComment; - this.ScriptRequiresComment = scriptRequiresComment; - this.ScriptContent = scriptRemainingContent; + ScriptMetadataComment = scriptMetadataComment; + ScriptHelpComment = scriptHelpComment; + ScriptRequiresComment = scriptRequiresComment; + ScriptContent = scriptRemainingContent; } /// @@ -86,10 +86,10 @@ public PSScriptFileInfo( PSScriptContents scriptRemainingContent ) { - this.ScriptMetadataComment = scriptMetadataComment; - this.ScriptHelpComment = scriptHelpComment; - this.ScriptRequiresComment = scriptRequiresComment; - this.ScriptContent = scriptRemainingContent; + ScriptMetadataComment = scriptMetadataComment; + ScriptHelpComment = scriptHelpComment; + ScriptRequiresComment = scriptRequiresComment; + ScriptContent = scriptRemainingContent; } #endregion @@ -289,14 +289,14 @@ internal static bool TryTestPSScriptFile( // this is for Uri errors, which aren't required by script but we check if those in the script aren't valid Uri's. out string[] verboseMsgs) { - verboseMsgs = new string[]{}; + verboseMsgs = Utils.EmptyStrArray; List errorsList = new List(); parsedScript = null; List psScriptInfoCommentContent = new List(); List helpInfoCommentContent = new List(); List requiresCommentContent = new List(); - string[] remainingFileContent = new string[]{}; + string[] remainingFileContent = Utils.EmptyStrArray; // Parse .ps1 contents out of file into list objects if (!TryParseScriptFileContents( diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs index f799600b9..76e40ebfa 100644 --- a/src/code/PSScriptHelp.cs +++ b/src/code/PSScriptHelp.cs @@ -28,42 +28,42 @@ public sealed class PSScriptHelp /// /// The example(s) relating to the script's usage. /// - public string[] Example { get; private set; } = new string[]{}; + public string[] Example { get; private set; } = Utils.EmptyStrArray; /// /// The inputs to the script. /// - public string[] Inputs { get; private set; } = new string[]{}; + public string[] Inputs { get; private set; } = Utils.EmptyStrArray; /// /// The outputs to the script. /// - public string[] Outputs { get; private set; } = new string[]{}; + public string[] Outputs { get; private set; } = Utils.EmptyStrArray; /// /// The notes for the script. /// - public string[] Notes { get; private set; } = new string[]{}; + public string[] Notes { get; private set; } = Utils.EmptyStrArray; /// /// The links for the script. /// - public string[] Links { get; private set; } = new string[]{}; + public string[] Links { get; private set; } = Utils.EmptyStrArray; /// /// The components for the script. /// - public string[] Component { get; private set; } = new string[]{}; + public string[] Component { get; private set; } = Utils.EmptyStrArray; /// /// The roles for the script. /// - public string[] Role { get; private set; } = new string[]{}; + public string[] Role { get; private set; } = Utils.EmptyStrArray; /// /// The functionality components for the script. /// - public string[] Functionality { get; private set; } = new string[]{}; + public string[] Functionality { get; private set; } = Utils.EmptyStrArray; #endregion @@ -122,8 +122,8 @@ internal PSScriptHelp() {} internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord error) { bool successfullyParsed = true; - char[] spaceDelimeter = new char[]{' '}; - char[] newlineDelimeter = new char[]{'\n'}; + string[] spaceDelimeter = new string[]{" "}; + string[] newlineDelimeter = new string[]{Environment.NewLine}; // parse content into a hashtable Hashtable parsedHelpMetadata = Utils.ParseCommentBlockContent(commentLines); @@ -211,7 +211,7 @@ internal string[] EmitContent() { List psHelpInfoLines = new List(); - psHelpInfoLines.Add("<#\n"); + psHelpInfoLines.Add($"<#{Environment.NewLine}"); psHelpInfoLines.Add($".DESCRIPTION"); psHelpInfoLines.Add($"{Description}{Environment.NewLine}"); diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs index be44b9d04..7ae90d4b0 100644 --- a/src/code/PSScriptMetadata.cs +++ b/src/code/PSScriptMetadata.cs @@ -64,17 +64,17 @@ public sealed class PSScriptMetadata /// /// the list of external module dependencies for the script. /// - public string[] ExternalModuleDependencies { get; private set; } = new string[]{}; + public string[] ExternalModuleDependencies { get; private set; } = Utils.EmptyStrArray; /// /// the list of required scripts for the parent script. /// - public string[] RequiredScripts { get; private set; } = new string[]{}; + public string[] RequiredScripts { get; private set; } = Utils.EmptyStrArray; /// /// the list of external script dependencies for the script. /// - public string[] ExternalScriptDependencies { get; private set; } = new string[]{}; + public string[] ExternalScriptDependencies { get; private set; } = Utils.EmptyStrArray; /// /// the release notes relating to the script. @@ -146,7 +146,7 @@ internal PSScriptMetadata() {} /// internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] errors, out string[] msgs) { - msgs = new string[]{}; + msgs = Utils.EmptyStrArray; List msgsList = new List(); // parse content into a hashtable @@ -169,7 +169,7 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error } // now populate the object instance - char[] spaceDelimeter = new char[]{' '}; + string[] spaceDelimeter = new string[]{" "}; Uri parsedLicenseUri = null; if (!String.IsNullOrEmpty((string) parsedMetadata["LICENSEURI"])) diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs index dabf34fde..420ca052c 100644 --- a/src/code/PSScriptRequires.cs +++ b/src/code/PSScriptRequires.cs @@ -33,7 +33,7 @@ public sealed class PSScriptRequires /// public PSScriptRequires(ModuleSpecification[] requiredModules) { - this.RequiredModules = requiredModules ?? new ModuleSpecification[]{}; + RequiredModules = requiredModules ?? new ModuleSpecification[]{}; } /// @@ -64,7 +64,7 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error errors = new ErrorRecord[]{}; List errorsList = new List(); - string requiresComment = String.Join("\n", commentLines); + string requiresComment = String.Join(Environment.NewLine, commentLines); try { diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 287d4956e..7b9c02bde 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -98,7 +98,7 @@ public static string QuoteName(string name) return "'" + CodeGeneration.EscapeSingleQuotedStringContent(name) + "'"; } - public static string[] GetStringArrayFromString(char[] delimeter, string stringToConvertToArray) + public static string[] GetStringArrayFromString(string[] delimeter, string stringToConvertToArray) { // this will be a string where entries are separated by space if (String.IsNullOrEmpty(stringToConvertToArray)) From 75e7f34e8f6105384ff2e407d9ec8d231ba5b9e3 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 19 Jul 2022 17:15:32 -0400 Subject: [PATCH 27/29] replace array initializer bracket with Array.Empty() --- src/code/NewPSScriptFileInfo.cs | 2 +- src/code/PSScriptFileInfo.cs | 2 +- src/code/PSScriptRequires.cs | 6 +++--- src/code/UpdatePSScriptFileInfo.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index baa3fa9bf..0e1bf0031 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -203,7 +203,7 @@ protected override void EndProcessing() ThrowTerminatingError(ScriptAtPathAlreadyExistsError); } - ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; + ModuleSpecification[] validatedRequiredModuleSpecifications = Array.Empty(); if (RequiredModules != null && RequiredModules.Length > 0) { if (!Utils.TryCreateModuleSpecification( diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 513f365c2..f3c4f3b5b 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -451,7 +451,7 @@ out ErrorRecord[] errors { psScriptFileContents = Utils.EmptyStrArray; List fileContentsList = new List(); - errors = new ErrorRecord[]{}; + errors = Array.Empty(); List errorsList = new List(); bool fileContentsSuccessfullyCreated = true; diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs index 420ca052c..4c2e551d4 100644 --- a/src/code/PSScriptRequires.cs +++ b/src/code/PSScriptRequires.cs @@ -22,7 +22,7 @@ public sealed class PSScriptRequires /// The list of modules required by the script. /// Hashtable keys: GUID, MaxVersion, ModuleName (Required), RequiredVersion, Version. /// - public ModuleSpecification[] RequiredModules { get; private set; } = new ModuleSpecification[]{}; + public ModuleSpecification[] RequiredModules { get; private set; } = Array.Empty(); #endregion @@ -33,7 +33,7 @@ public sealed class PSScriptRequires /// public PSScriptRequires(ModuleSpecification[] requiredModules) { - RequiredModules = requiredModules ?? new ModuleSpecification[]{}; + RequiredModules = requiredModules ?? Array.Empty(); } /// @@ -62,7 +62,7 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error */ - errors = new ErrorRecord[]{}; + errors = Array.Empty(); List errorsList = new List(); string requiresComment = String.Join(Environment.NewLine, commentLines); diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 7fac81056..8803873d7 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -204,7 +204,7 @@ protected override void EndProcessing() ThrowTerminatingError(FileDoesNotExistError); } - ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; + ModuleSpecification[] validatedRequiredModuleSpecifications = Array.Empty(); if (RequiredModules != null && RequiredModules.Length > 0) { if (!Utils.TryCreateModuleSpecification( From ce039ed0780be0e1e24463c4db250fa9c676c649 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 19 Jul 2022 17:18:39 -0400 Subject: [PATCH 28/29] one more bracket initializer and one capitalization typo --- src/code/Utils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 7b9c02bde..977d0be8a 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -100,7 +100,7 @@ public static string QuoteName(string name) public static string[] GetStringArrayFromString(string[] delimeter, string stringToConvertToArray) { - // this will be a string where entries are separated by space + // This will be a string where entries are separated by space. if (String.IsNullOrEmpty(stringToConvertToArray)) { return Utils.EmptyStrArray; @@ -933,7 +933,7 @@ public static bool TryCreateModuleSpecification( { bool moduleSpecCreatedSuccessfully = true; List errorList = new List(); - validatedModuleSpecs = new ModuleSpecification[]{}; + validatedModuleSpecs = Array.Empty(); List moduleSpecsList = new List(); foreach(Hashtable moduleSpec in moduleSpecHashtables) From 761f3a90729c3ad2afe72f9ae7c8ea0c5bcf84ad Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 19 Jul 2022 18:02:06 -0400 Subject: [PATCH 29/29] add note about extra newline for Metadata and HelpInfo emitted content --- src/code/PSScriptHelp.cs | 1 + src/code/PSScriptMetadata.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs index 76e40ebfa..9964d2159 100644 --- a/src/code/PSScriptHelp.cs +++ b/src/code/PSScriptHelp.cs @@ -209,6 +209,7 @@ internal bool ValidateContent(out ErrorRecord error) /// internal string[] EmitContent() { + // Note: we add a newline to the end of each property entry in HelpInfo so that there's an empty line separating them. List psHelpInfoLines = new List(); psHelpInfoLines.Add($"<#{Environment.NewLine}"); diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs index 7ae90d4b0..ded8088e7 100644 --- a/src/code/PSScriptMetadata.cs +++ b/src/code/PSScriptMetadata.cs @@ -150,7 +150,6 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error List msgsList = new List(); // parse content into a hashtable - // Hashtable parsedMetadata = ParseContent(commentLines); Hashtable parsedMetadata = Utils.ParseCommentBlockContent(commentLines); if (parsedMetadata.Count == 0) @@ -338,6 +337,7 @@ Feature 5 List psScriptInfoLines = new List(); + // Note: we add a newline to the end of each property entry in HelpInfo so that there's an empty line separating them. psScriptInfoLines.Add($"<#PSScriptInfo{Environment.NewLine}"); psScriptInfoLines.Add($".VERSION {Version.ToString()}{Environment.NewLine}"); psScriptInfoLines.Add($".GUID {Guid.ToString()}{Environment.NewLine}");