diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index 2c9b767b2..2b4953d39 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -21,6 +21,9 @@ 'Register-PSResourceRepository', 'Save-PSResource', 'Set-PSResourceRepository', + 'New-PSScriptFileInfo', + 'Test-PSScriptFileInfo', + 'Update-PSScriptFileInfo', 'Publish-PSResource', 'Uninstall-PSResource', 'Unregister-PSResourceRepository', diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs new file mode 100644 index 000000000..0e1bf0031 --- /dev/null +++ b/src/code/NewPSScriptFileInfo.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, "PSScriptFileInfo")] + public sealed class NewPSScriptFileInfo : 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 = Array.Empty(); + if (RequiredModules != null && RequiredModules.Length > 0) + { + if (!Utils.TryCreateModuleSpecification( + moduleSpecHashtables: RequiredModules, + out validatedRequiredModuleSpecifications, + out ErrorRecord[] moduleSpecErrors)) + { + foreach (ErrorRecord err in moduleSpecErrors) + { + WriteError(err); + } + + return; + } + } + + PSScriptFileInfo scriptInfo = new PSScriptFileInfo( + 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( + psScriptFileContents: out string[] psScriptFileContents, + errors: out ErrorRecord[] errors)) + { + foreach (ErrorRecord err in errors) + { + WriteError(err); + } + + return; + } + + File.WriteAllLines(resolvedFilePath, psScriptFileContents); + } + + #endregion + } +} diff --git a/src/code/PSScriptContents.cs b/src/code/PSScriptContents.cs new file mode 100644 index 000000000..c04f1dc49 --- /dev/null +++ b/src/code/PSScriptContents.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +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; } = Utils.EmptyStrArray; + + /// + /// End of file contents for the .ps1 file. + /// + public bool ContainsSignature { get; set; } = false; + + #endregion + + #region Private Members + + private const string signatureStartString = "# SIG # Begin signature block"; + private int _signatureStartIndex = -1; + + #endregion + + #region Constructor + + /// + /// This constructor takes end of file contents as a string and checks if it has a signature. + /// + public PSScriptContents(string[] endOfFileContents) + { + EndOfFileContents = endOfFileContents; + 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 + + #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) + { + EndOfFileContents = commentLines; + ContainsSignature = CheckForSignature(); + } + } + + /// + /// 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. + /// When emitting contents, any file signature is always removed because it is invalidated when the content is updated. + /// + internal string[] EmitContent() + { + RemoveSignatureString(); + return EndOfFileContents; + } + + #endregion + + #region Private Methods + + /// + /// Checks if the end of file contents contain a signature. + /// + private bool CheckForSignature() + { + for (int i = 0; i < EndOfFileContents.Length; i++) + { + if (String.Equals(EndOfFileContents[i], signatureStartString, StringComparison.InvariantCultureIgnoreCase)) + { + _signatureStartIndex = i; + } + } + + return _signatureStartIndex != -1; + } + + /// + /// Removes the signature from EndOfFileContents property + /// as the signature would be invalidated during update. + /// + private void RemoveSignatureString() + { + if (ContainsSignature) + { + string[] newEndOfFileContents = new string[EndOfFileContents.Length - _signatureStartIndex]; + Array.Copy(EndOfFileContents, newEndOfFileContents, _signatureStartIndex); + EndOfFileContents = newEndOfFileContents; + + ContainsSignature = false; + } + } + #endregion + } +} diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs new file mode 100644 index 000000000..f3c4f3b5b --- /dev/null +++ b/src/code/PSScriptFileInfo.cs @@ -0,0 +1,492 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Linq; +using Microsoft.PowerShell.Commands; + +namespace Microsoft.PowerShell.PowerShellGet.UtilClasses +{ + /// + /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents). + /// + public sealed class PSScriptFileInfo + { + #region Properties + public PSScriptMetadata ScriptMetadataComment { get; set; } + + public PSScriptHelp ScriptHelpComment { get; set; } + + public PSScriptRequires ScriptRequiresComment { get; set; } + + public PSScriptContents ScriptContent { get; set; } + + #endregion + + #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, + 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, + externalModuleDependencies, + requiredScripts, + externalScriptDependencies, + releaseNotes, + privateData); + + PSScriptHelp scriptHelpComment = new PSScriptHelp(description); + PSScriptRequires scriptRequiresComment = new PSScriptRequires(requiredModules); + PSScriptContents scriptRemainingContent = new PSScriptContents(Utils.EmptyStrArray); + + ScriptMetadataComment = scriptMetadataComment; + ScriptHelpComment = scriptHelpComment; + ScriptRequiresComment = scriptRequiresComment; + 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, + PSScriptRequires scriptRequiresComment, + PSScriptContents scriptRemainingContent + ) + { + ScriptMetadataComment = scriptMetadataComment; + ScriptHelpComment = scriptHelpComment; + ScriptRequiresComment = scriptRequiresComment; + ScriptContent = scriptRemainingContent; + } + + #endregion + + #region Internal Static Methods + + /// + /// Parses .ps1 file contents for PSScriptInfo, PSHelpInfo, Requires comments + /// + internal static bool TryParseScriptFileContents( + string scriptFileInfoPath, + ref List psScriptInfoCommentContent, + ref List helpInfoCommentContent, + ref List requiresCommentContent, + ref string[] remainingFileContent, + out ErrorRecord error) + { + error= null; + + psScriptInfoCommentContent = new List(); + helpInfoCommentContent = new List(); + requiresCommentContent = new List(); + remainingFileContent = Utils.EmptyStrArray; + + string[] fileContents = File.ReadAllLines(scriptFileInfoPath); + + bool reachedPSScriptInfoCommentEnd = false; + bool reachedHelpInfoCommentEnd = 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("#>")) + { + reachedPSScriptInfoCommentEnd = true; + i = j + 1; + break; + } + + psScriptInfoCommentContent.Add(blockLine); + j++; + } + + 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); + error = new ErrorRecord(ex, "MissingEndBracketToPSScriptInfoParseError", ErrorCategory.ParserError, null); + return false; + } + } + else if (line.StartsWith("<#")) + { + // 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) + { + string blockLine = fileContents[j]; + if (blockLine.StartsWith("#>")) + { + reachedHelpInfoCommentEnd = true; + i = j + 1; + endOfFileContentsStartIndex = i; + break; + } + + helpInfoCommentContent.Add(blockLine); + j++; + } + + 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); + error = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); + return false; + } + } + else if (line.StartsWith("#Requires")) + { + requiresCommentContent.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, if any + remainingFileContent = new string[fileContents.Length - endOfFileContentsStartIndex]; + + Array.Copy(fileContents, endOfFileContentsStartIndex, remainingFileContent, 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); + error = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); + return false; + } + + 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); + error = new ErrorRecord(ex, "missingHelpInfoCommentError", ErrorCategory.ParserError, null); + return false; + } + + 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, + out verboseMsgs)) + { + errorsList.AddRange(metadataErrors); + parsedContentSuccessfully = false; + } + + currentHelpInfo = new PSScriptHelp(); + if (!currentHelpInfo.ParseContentIntoObj( + commentLines: helpInfoCommentContent.ToArray(), + out ErrorRecord helpError)) + { + errorsList.Add(helpError); + parsedContentSuccessfully = false; + } + + currentRequiresComment = new PSScriptRequires(); + if (!currentRequiresComment.ParseContentIntoObj( + commentLines: requiresCommentContent.ToArray(), + out ErrorRecord[] requiresErrors)) + { + errorsList.AddRange(requiresErrors); + parsedContentSuccessfully = false; + } + + 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, + // 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 = Utils.EmptyStrArray; + List errorsList = new List(); + parsedScript = null; + + List psScriptInfoCommentContent = new List(); + List helpInfoCommentContent = new List(); + List requiresCommentContent = new List(); + string[] remainingFileContent = Utils.EmptyStrArray; + + // 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; + } + + // 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)) + { + return false; + } + + // Create PSScriptFileInfo instance with script metadata class instances (PSScriptMetadata, PSScriptHelp, PSScriptRequires, PSScriptContents) + 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); + errors = new ErrorRecord[]{PSScriptFileInfoObjectNotCreatedFromFileError}; + return false; + } + + errors = errorsList.ToArray(); + return true; + } + + /// + /// 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 = Utils.EmptyStrArray; + List errorsList = new List(); + bool successfullyUpdated = true; + + if (scriptInfo == null) + { + throw new ArgumentNullException(nameof(scriptInfo)); + } + + if (!scriptInfo.ScriptMetadataComment.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)) + { + 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( + psScriptFileContents: 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[] psScriptFileContents, + out ErrorRecord[] errors + ) + { + psScriptFileContents = Utils.EmptyStrArray; + List fileContentsList = new List(); + errors = Array.Empty(); + List errorsList = new List(); + + bool fileContentsSuccessfullyCreated = true; + + // Step 1: validate object properties for required script properties. + if (!ScriptMetadataComment.ValidateContent(out ErrorRecord[] metadataValidationErrors)) + { + errorsList.AddRange(metadataValidationErrors); + fileContentsSuccessfullyCreated = false; + } + + if (!ScriptHelpComment.ValidateContent(out ErrorRecord helpValidationError)) + { + errorsList.Add(helpValidationError); + fileContentsSuccessfullyCreated = false; + } + + if (!fileContentsSuccessfullyCreated) + { + errors = errorsList.ToArray(); + return fileContentsSuccessfullyCreated; + } + + // Step 2: create string [] that will be used to write to file later + fileContentsList.AddRange(ScriptMetadataComment.EmitContent()); + + // string psRequiresCommentBlock = ScriptRequiresComment.EmitContent(); + fileContentsList.AddRange(ScriptRequiresComment.EmitContent()); + fileContentsList.AddRange(ScriptHelpComment.EmitContent()); + fileContentsList.AddRange(ScriptContent.EmitContent()); + + psScriptFileContents = fileContentsList.ToArray(); + return fileContentsSuccessfullyCreated; + } + + #endregion + } +} diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs new file mode 100644 index 000000000..9964d2159 --- /dev/null +++ b/src/code/PSScriptHelp.cs @@ -0,0 +1,325 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation; + +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; } = Utils.EmptyStrArray; + + /// + /// The inputs to the script. + /// + public string[] Inputs { get; private set; } = Utils.EmptyStrArray; + + /// + /// The outputs to the script. + /// + public string[] Outputs { get; private set; } = Utils.EmptyStrArray; + + /// + /// The notes for the script. + /// + public string[] Notes { get; private set; } = Utils.EmptyStrArray; + + /// + /// The links for the script. + /// + public string[] Links { get; private set; } = Utils.EmptyStrArray; + + /// + /// The components for the script. + /// + public string[] Component { get; private set; } = Utils.EmptyStrArray; + + /// + /// The roles for the script. + /// + public string[] Role { get; private set; } = Utils.EmptyStrArray; + + /// + /// The functionality components for the script. + /// + public string[] Functionality { get; private set; } = Utils.EmptyStrArray; + + #endregion + + #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, + 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; + } + + /// + /// 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 + + #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; + string[] spaceDelimeter = new string[]{" "}; + string[] newlineDelimeter = new string[]{Environment.NewLine}; + + // parse content into a hashtable + Hashtable parsedHelpMetadata = Utils.ParseCommentBlockContent(commentLines); + + if (!ValidateParsedContent(parsedHelpMetadata, out error)) + { + return false; + } + + // populate object + Description = (string) parsedHelpMetadata["DESCRIPTION"]; + 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"]); + + return successfullyParsed; + } + + /// + /// 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; + 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; + } + + /// + /// Validates help info properties contain required script Help properties + /// i.e Description. + /// + 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; + } + + /// + /// Emits string representation of 'HelpInfo <# ... #>' comment and its metadata contents. + /// + 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}"); + psHelpInfoLines.Add($".DESCRIPTION"); + psHelpInfoLines.Add($"{Description}{Environment.NewLine}"); + + if (!String.IsNullOrEmpty(Synopsis)) + { + psHelpInfoLines.Add($".SYNOPSIS"); + psHelpInfoLines.Add($"{Synopsis}{Environment.NewLine}"); + } + + foreach (string currentExample in Example) + { + psHelpInfoLines.Add($".EXAMPLE"); + psHelpInfoLines.Add($"{currentExample}{Environment.NewLine}"); + } + + foreach (string input in Inputs) + { + psHelpInfoLines.Add($".INPUTS"); + psHelpInfoLines.Add($"{input}{Environment.NewLine}"); + } + + foreach (string output in Outputs) + { + psHelpInfoLines.Add($".OUTPUTS"); + psHelpInfoLines.Add($"{output}{Environment.NewLine}"); + } + + if (Notes.Length > 0) + { + psHelpInfoLines.Add($".NOTES"); + psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Notes)}{Environment.NewLine}"); + } + + foreach (string link in Links) + { + psHelpInfoLines.Add($".LINK"); + psHelpInfoLines.Add($"{link}{Environment.NewLine}"); + } + + if (Component.Length > 0) + { + psHelpInfoLines.Add($".COMPONENT"); + psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Component)}{Environment.NewLine}"); + } + + if (Role.Length > 0) + { + psHelpInfoLines.Add($".ROLE"); + psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Role)}{Environment.NewLine}"); + } + + if (Functionality.Length > 0) + { + psHelpInfoLines.Add($".FUNCTIONALITY"); + psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Functionality)}{Environment.NewLine}"); + } + + psHelpInfoLines.Add("#>"); + + return psHelpInfoLines.ToArray(); + } + + /// + /// Updates contents of the HelpInfo properties from any (non-default) values passed in. + /// + 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 + + /// + /// Ensure description field (passed as stringToValidate) does not contain '<#' or '#>'. + /// + private bool StringContainsComment(string stringToValidate) + { + return stringToValidate.Contains("<#") || stringToValidate.Contains("#>"); + } + + #endregion + } +} diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs new file mode 100644 index 000000000..ded8088e7 --- /dev/null +++ b/src/code/PSScriptMetadata.cs @@ -0,0 +1,458 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation; +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; } = Utils.EmptyStrArray; + + /// + /// the list of required scripts for the parent script. + /// + public string[] RequiredScripts { get; private set; } = Utils.EmptyStrArray; + + /// + /// the list of external script dependencies for the script. + /// + public string[] ExternalScriptDependencies { get; private set; } = Utils.EmptyStrArray; + + /// + /// 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 + + /// + /// This constructor takes metadata properties and creates PSScriptMetadata instance. + /// + 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; + } + + /// + /// 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 + + #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 = Utils.EmptyStrArray; + List msgsList = new List(); + + // parse content into a hashtable + Hashtable parsedMetadata = Utils.ParseCommentBlockContent(commentLines); + + 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 + string[] spaceDelimeter = new string[]{" "}; + + 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; + } + + /// + /// 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(); + + 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 properties are 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 + #> + */ + + 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(); + + // 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}"); + 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 psScriptInfoLines.ToArray(); + } + + /// + /// Updates contents of the script metadata properties from any (non-default) values passed in. + /// + 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 + } +} diff --git a/src/code/PSScriptRequires.cs b/src/code/PSScriptRequires.cs new file mode 100644 index 000000000..4c2e551d4 --- /dev/null +++ b/src/code/PSScriptRequires.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Linq; +using System.Collections.ObjectModel; +using Microsoft.PowerShell.Commands; + +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; } = Array.Empty(); + + #endregion + + #region Constructor + + /// + /// This constructor creates a new PSScriptRequires instance with specified required modules. + /// + public PSScriptRequires(ModuleSpecification[] requiredModules) + { + RequiredModules = requiredModules ?? Array.Empty(); + } + + /// + /// 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 + + #region Internal Methods + + /// + /// Parses RequiredModules out of comment lines and validates during parse process. + /// + internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] errors) + { + /** + When Requires comment lines are obtained from .ps1 file they will have this format: + + #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 = Array.Empty(); + List errorsList = new List(); + string requiresComment = String.Join(Environment.NewLine, 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 {0}.", 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; + } + + /// + /// Emits string representation of '#Requires ...' comment(s). + /// + internal string[] EmitContent() + { + List psRequiresLines = new List(); + if (RequiredModules.Length > 0) + { + psRequiresLines.Add(String.Empty); + foreach (ModuleSpecification moduleSpec in RequiredModules) + { + psRequiresLines.Add(String.Format("#Requires -Module {0}", moduleSpec.ToString())); + } + + psRequiresLines.Add(String.Empty); + } + + return psRequiresLines.ToArray(); + } + + /// + /// Updates the current Requires content with another (passed in), effectively replaces it. + /// + internal void UpdateContent(ModuleSpecification[] requiredModules) + { + if (requiredModules != null && requiredModules.Length != 0){ + RequiredModules = requiredModules; + } + } + + #endregion + } +} diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs new file mode 100644 index 000000000..931d04c7c --- /dev/null +++ b/src/code/TestPSScriptFileInfo.cs @@ -0,0 +1,85 @@ +// 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.TryTestPSScriptFile( + scriptFileInfoPath: resolvedFilePath, + parsedScript: out PSScriptFileInfo _, + errors: out ErrorRecord[] errors, + out string[] verboseMsgs); + + if (!isValidScript) + { + foreach (ErrorRecord error in errors) + { + WriteVerbose("The .ps1 script file passed in was not valid due to: " + error.Exception.Message); + } + } + + foreach (string msg in verboseMsgs) + { + WriteVerbose(msg); + } + + WriteObject(isValidScript); + } + + #endregion + } +} diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs new file mode 100644 index 000000000..8803873d7 --- /dev/null +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -0,0 +1,319 @@ +// 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 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 = Array.Empty(); + 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.TryTestPSScriptFile( + scriptFileInfoPath: resolvedFilePath, + parsedScript: out PSScriptFileInfo parsedScriptInfo, + errors: out ErrorRecord[] errors, + out string[] verboseMsgs)) + { + foreach (string msg in verboseMsgs) + { + WriteVerbose(msg); + } + + foreach (ErrorRecord error in errors) + { + WriteError(error); + } + + return; + } + + bool signatureRemoved = false; + if (parsedScriptInfo.ScriptContent.ContainsSignature) + { + 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); + } + + signatureRemoved = true; + } + + 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.WriteAllLines(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); + } + } + + if (signatureRemoved) + { + WriteWarning("Re-sign this script, as the original signature was removed during update."); + } + } + + #endregion + } +} diff --git a/src/code/Utils.cs b/src/code/Utils.cs index f19861847..977d0be8a 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(string[] 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. /// @@ -914,6 +926,185 @@ 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 = Array.Empty(); + 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; + } + + /// + /// 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 diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index 3ba34acf3..4b48ccd57 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$script:newline*$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$script:newline*$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 - } - #> -} diff --git a/test/UpdatePSScriptFileInfo.Tests.ps1 b/test/UpdatePSScriptFileInfo.Tests.ps1 new file mode 100644 index 000000000..6eac2d1d0 --- /dev/null +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -0,0 +1,314 @@ +# 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" + + $script:newline = [System.Environment]::NewLine; + } + + 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$script:newline*$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$script:newline*$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$script:newline*$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$script:newline*$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