diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index 4029ab2bf..b98d29797 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -21,10 +21,13 @@ 'Register-PSResourceRepository', 'Save-PSResource', 'Set-PSResourceRepository', + 'New-PSScriptFileInfo', 'Publish-PSResource', + 'Test-PSScriptFileInfo', 'Uninstall-PSResource', 'Unregister-PSResourceRepository', - 'Update-PSResource') + 'Update-PSResource', + 'Update-PSScriptFileInfo') VariablesToExport = 'PSGetPath' AliasesToExport = @('inmo', 'fimo', 'upmo', 'pumo') diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs new file mode 100644 index 000000000..8061688a1 --- /dev/null +++ b/src/code/NewPSScriptFileInfo.cs @@ -0,0 +1,259 @@ +// 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 = new ModuleSpecification[]{}; + if (RequiredModules != null && RequiredModules.Length > 0) + { + if (!Utils.TryCreateModuleSpecification( + moduleSpecHashtables: RequiredModules, + out validatedRequiredModuleSpecifications, + out ErrorRecord[] moduleSpecErrors)) + { + foreach (ErrorRecord err in moduleSpecErrors) + { + WriteError(err); + } + + return; + } + } + + 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, + endOfFileContents: String.Empty); + + if (!scriptInfo.TryCreateScriptFileInfoString( + pSScriptFileString: out string psScriptFileContents, + errors: out ErrorRecord[] errors)) + { + foreach (ErrorRecord err in errors) + { + WriteError(err); + } + + return; + } + + File.WriteAllText(resolvedFilePath, psScriptFileContents); + } + + #endregion + } +} diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs new file mode 100644 index 000000000..065c3aef0 --- /dev/null +++ b/src/code/PSScriptFileInfo.cs @@ -0,0 +1,1183 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Linq; +using System.Collections.ObjectModel; +using Microsoft.PowerShell.Commands; +using NuGet.Versioning; + +namespace Microsoft.PowerShell.PowerShellGet.UtilClasses +{ + /// + /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents). + /// + public sealed class PSScriptFileInfo + { + + #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 modules required by the script. + /// Hashtable keys: GUID, MaxVersion, ModuleName (Required), RequiredVersion, Version. + /// + public ModuleSpecification[] RequiredModules { get; private set; } = new ModuleSpecification[]{}; + + /// + /// the list of external module dependencies for the script. + /// + public string[] ExternalModuleDependencies { get; private set; } = new string[]{}; + + /// + /// the list of required scripts for the parent script. + /// + public string[] RequiredScripts { get; private set; } = new string[]{}; + + /// + /// the list of external script dependencies for the script. + /// + public string[] ExternalScriptDependencies { get; private set; } = new string[]{}; + + /// + /// the release notes relating to the script. + /// + public string ReleaseNotes { get; private set; } = String.Empty; + + /// + /// The private data associated with the script. + /// + public string PrivateData { get; private set; } + + /// + /// The description of the script. + /// + public string Description { get; private set; } + + /// + /// End of file contents for the .ps1 file. + /// + public string EndOfFileContents { get; private set; } = String.Empty; + + /// + /// The synopsis of the script. + /// + public string Synopsis { get; private set; } + + /// + /// The example(s) relating to the script's usage. + /// + public string[] Example { get; private set; } = new string[]{}; + + /// + /// The inputs to the script. + /// + public string[] Inputs { get; private set; } = new string[]{}; + + /// + /// The outputs to the script. + /// + public string[] Outputs { get; private set; } = new string[]{}; + + /// + /// The notes for the script. + /// + public string[] Notes { get; private set; } = new string[]{}; + + /// + /// The links for the script. + /// + public string[] Links { get; private set; } = new string[]{}; + + /// + /// The components for the script. + /// + public string[] Component { get; private set; } = new string[]{}; + + /// + /// The roles for the script. + /// + public string[] Role { get; private set; } = new string[]{}; + + /// + /// The functionality components for the script. + /// + public string[] Functionality { get; private set; } = new string[]{}; + + #endregion + + #region Private Members + + private const string signatureStartString = "# SIG # Begin signature block"; + + #endregion + + #region Constructor + + private PSScriptFileInfo() {} + 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, + string endOfFileContents) + { + 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; + RequiredModules = requiredModules ?? new ModuleSpecification[]{}; + ExternalModuleDependencies = externalModuleDependencies ?? Utils.EmptyStrArray; + RequiredScripts = requiredScripts ?? Utils.EmptyStrArray; + ExternalScriptDependencies = externalScriptDependencies ?? Utils.EmptyStrArray; + ReleaseNotes = releaseNotes; + PrivateData = privateData; + Description = description; + EndOfFileContents = endOfFileContents; + } + + #endregion + + #region Internal Static Methods + + internal static bool TryParseScriptFile2( + string scriptFileInfoPath, + out Hashtable parsedScriptMetadata, + out ErrorRecord error + ) + { + parsedScriptMetadata = new Hashtable(); + error = null; + + string[] fileContents = File.ReadAllLines(scriptFileInfoPath); + List psScriptInfoCommentContent = new List(); + List helpInfoCommentContent = new List(); + List requiresContent = new List(); + string[] remainingFileContentArray; + + bool gotEndToPSSCriptInfoContent = false; + bool gotEndToHelpInfoContent = false; + + int i = 0; + int endOfFileContentsStartIndex = 0; + while (i < fileContents.Length) + { + string line = fileContents[i]; + + if (line.StartsWith("<#PSScriptInfo")) + { + int j = i + 1; // start at the next line + // keep grabbing lines until we get to closing #> + while (j < fileContents.Length) + { + string blockLine = fileContents[j]; + if (blockLine.StartsWith("#>")) + { + gotEndToPSSCriptInfoContent = true; + i = j + 1; + break; + } + + psScriptInfoCommentContent.Add(blockLine); + j++; + } + + if (!gotEndToPSSCriptInfoContent) + { + var message = String.Format("Could not parse '{0}' as a PowerShell script file due to missing the closing '#>' for <#PSScriptInfo comment block", scriptFileInfoPath); + var ex = new InvalidOperationException(message); + error = new ErrorRecord(ex, "MissingEndBracketToPSScriptInfoParseError", ErrorCategory.ParserError, null); + return false; + } + } + else if (line.StartsWith("<#")) + { + // we assume the next comment block should be the help comment block (containing description) + // keep grabbing lines until we get to closing #> + int j = i + 1; + while (j < fileContents.Length) + { + string blockLine = fileContents[j]; + if (blockLine.StartsWith("#>")) + { + gotEndToHelpInfoContent = true; + i = j + 1; + endOfFileContentsStartIndex = i; + break; + } + + helpInfoCommentContent.Add(blockLine); + j++; + } + + if (!gotEndToHelpInfoContent) + { + var message = String.Format("Could not parse '{0}' as a PowerShell script file due to missing the closing '#>' for HelpInfo comment block", scriptFileInfoPath); + var ex = new InvalidOperationException(message); + error = new ErrorRecord(ex, "MissingEndBracketToHelpInfoCommentParseError", ErrorCategory.ParserError, null); + return false; + } + } + else if (line.StartsWith("#Requires")) + { + requiresContent.Add(line); + i++; + } + else if (endOfFileContentsStartIndex != 0) + { + break; + } + else + { + // this would be newlines between blocks, or if there was other (unexpected) data between PSScriptInfo, Requires, and HelpInfo blocks + i++; + } + } + + if (endOfFileContentsStartIndex != 0 && (endOfFileContentsStartIndex < fileContents.Length)) + { + // from this line to fileContents.Length is the endOfFileContents + // save it to append to end of file during Update + remainingFileContentArray = new string[fileContents.Length - endOfFileContentsStartIndex]; + Array.Copy(fileContents, endOfFileContentsStartIndex, remainingFileContentArray, 0, (fileContents.Length - endOfFileContentsStartIndex)); + } + + return true; + } + + /// + /// Parses content of .ps1 file into a hashtable. + /// + internal static bool TryParseScriptFile( + string scriptFileInfoPath, + out Hashtable parsedScriptMetadata, + out string endOfFileContents, + out ErrorRecord[] errors) + { + errors = new ErrorRecord[]{}; + parsedScriptMetadata = new Hashtable(StringComparer.InvariantCultureIgnoreCase); + endOfFileContents = String.Empty; + List errorsList = new List(); + + // Parse the script file + var ast = Parser.ParseFile( + scriptFileInfoPath, + out Token[] tokens, + out ParseError[] parserErrors); + + if (parserErrors.Length > 0) + { + bool parseSuccessful = true; + foreach (ParseError err in parserErrors) + { + // we ignore WorkFlowNotSupportedInPowerShellCore errors, as this is common in scripts currently on PSGallery + if (!String.Equals(err.ErrorId, "WorkflowNotSupportedInPowerShellCore", StringComparison.OrdinalIgnoreCase)) + { + var message = String.Format("Could not parse '{0}' as a PowerShell script file due to {1}.", scriptFileInfoPath, err.Message); + var ex = new InvalidOperationException(message); + var psScriptFileParseError = new ErrorRecord(ex, err.ErrorId, ErrorCategory.ParserError, null); + errorsList.Add(psScriptFileParseError); + parseSuccessful = false; + } + } + + if (!parseSuccessful) + { + errors = errorsList.ToArray(); + return parseSuccessful; + } + } + + if (ast == null) + { + var parseFileException = new InvalidOperationException( + message: "Cannot parse .ps1 file", innerException: new ParseException( + message: "Parsed AST was null for .ps1 file")); + var astCouldNotBeCreatedError = new ErrorRecord(parseFileException, "ASTCouldNotBeCreated", ErrorCategory.ParserError, null); + + errorsList.Add(astCouldNotBeCreatedError); + errors = errorsList.ToArray(); + return false; + } + + // Get .DESCRIPTION property (required property), by accessing the Help block which contains .DESCRIPTION + CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); + if (scriptCommentInfo == null) + { + var message = String.Format("PSScript file is missing the required Description comment block in the script contents."); + var ex = new ArgumentException(message); + var psScriptMissingHelpContentCommentBlockError = new ErrorRecord(ex, "PSScriptMissingHelpContentCommentBlock", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingHelpContentCommentBlockError); + errors = errorsList.ToArray(); + return false; + } + + if (!String.IsNullOrEmpty(scriptCommentInfo.Description) && !scriptCommentInfo.Description.Contains("<#") && !scriptCommentInfo.Description.Contains("#>")) + { + parsedScriptMetadata.Add("DESCRIPTION", scriptCommentInfo.Description); + } + else + { + var message = String.Format("PSScript is missing the required Description property or Description value contains '<#' or '#>' which is invalid"); + var ex = new ArgumentException(message); + var psScriptMissingDescriptionOrInvalidPropertyError = new ErrorRecord(ex, "psScriptDescriptionMissingOrInvalidDescription", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingDescriptionOrInvalidPropertyError); + 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) + { + parsedModules = parsedScriptRequirements.RequiredModules; + parsedScriptMetadata.Add("REQUIREDMODULES", parsedModules); + } + + // Get the block/group comment beginning with <#PSScriptInfo + // this contains other script properties, including AUTHOR, VERSION, GUID (which are required properties) + List commentTokens = tokens.Where(a => a.Kind == TokenKind.Comment).ToList(); + string commentPattern = @"^<#PSScriptInfo"; + Regex rg = new Regex(commentPattern); + + Token psScriptInfoCommentToken = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).FirstOrDefault(); + if (psScriptInfoCommentToken == null) + { + var message = String.Format("PSScriptInfo comment was missing or could not be parsed"); + var ex = new ArgumentException(message); + var psCommentMissingError = new ErrorRecord(ex, "psScriptInfoCommentMissingError", ErrorCategory.ParserError, null); + errorsList.Add(psCommentMissingError); + + errors = errorsList.ToArray(); + return false; + } + + /** + should now have a Token with text like this: + + <#PSScriptInfo + + .VERSION 1.0 + + .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd + + .AUTHOR + + .COMPANYNAME + + .COPYRIGHT + + .TAGS + + .LICENSEURI + + .PROJECTURI + + .ICONURI + + .EXTERNALMODULEDEPENDENCIES + + .REQUIREDSCRIPTS + + .EXTERNALSCRIPTDEPENDENCIES + + .RELEASENOTES + + + .PRIVATEDATA + + #> + */ + + string[] newlineDelimeters = new string[]{"\r\n", "\n"}; + string[] commentLines = psScriptInfoCommentToken.Text.Split(newlineDelimeters, StringSplitOptions.RemoveEmptyEntries); + string keyName = String.Empty; + string value = String.Empty; + + /** + If comment line count is not more than two, it doesn't have the any metadata property and + comment block would look like: + + <#PSScriptInfo + #> + + */ + + if (commentLines.Count() <= 2) + { + var message = String.Format("PSScriptInfo comment block is empty and did not contain any metadata"); + var ex = new ArgumentException(message); + var psScriptInfoCommentEmptyError = new ErrorRecord(ex, "psScriptInfoCommentEmpty", ErrorCategory.ParserError, null); + errorsList.Add(psScriptInfoCommentEmptyError); + + errors = errorsList.ToArray(); + return false; + } + + GetMetadataFromCommentLines(commentLines, ref parsedScriptMetadata); + + // get end of file contents + string[] totalFileContents = File.ReadAllLines(scriptFileInfoPath); + var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); + endOfFileContents = String.Join("\n", contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToArray()); + + return true; + } + + /// + /// Takes hashtable (containing parsed .ps1 file content properties) and validates required properties are present. + /// + private static bool TryValidateScript( + Hashtable parsedScriptMetadata, + out ErrorRecord[] errors) + { + // required properties for script file (.ps1 file) are: Author, Version, Guid, Description + // Description gets validated in TryParseScript() when getting the property + + List errorsList = new List(); + + if (!parsedScriptMetadata.ContainsKey("VERSION") || String.IsNullOrEmpty((string) parsedScriptMetadata["VERSION"])) + { + 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 (!parsedScriptMetadata.ContainsKey("AUTHOR") || String.IsNullOrEmpty((string) parsedScriptMetadata["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); + } + + if (!parsedScriptMetadata.ContainsKey("GUID") || String.IsNullOrEmpty((string) parsedScriptMetadata["GUID"])) + { + 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); + } + + /// + /// Tests the contents of the .ps1 file at the provided path. + /// + internal static bool TryParseScriptIntoPSScriptInfo( + string scriptFileInfoPath, + out PSScriptFileInfo parsedScript, + out ErrorRecord[] errors, + out string[] verboseMsgs) + { + parsedScript = null; + errors = new ErrorRecord[]{}; + verboseMsgs = new string[]{}; + List errorsList = new List(); + List verboseMsgsList = new List(); + + if (!scriptFileInfoPath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + { + var message = String.Format("File passed in: {0} does not have a .ps1 extension as required", scriptFileInfoPath); + var ex = new ArgumentException(message); + var psScriptFileParseError = new ErrorRecord(ex, "ps1FileRequiredError", ErrorCategory.ParserError, null); + errorsList.Add(psScriptFileParseError); + errors = errorsList.ToArray(); + return false; + } + + if (!TryParseScriptFile( + scriptFileInfoPath: scriptFileInfoPath, + parsedScriptMetadata: out Hashtable parsedScriptMetadata, + endOfFileContents: out string endofFileContents, + errors: out ErrorRecord[] parseErrors + )) + { + errorsList.AddRange(parseErrors); + errors = errorsList.ToArray(); + return false; + } + + TryParseScriptFile2(scriptFileInfoPath, out Hashtable _, out ErrorRecord _); + + // at this point we've parsed into hashtable, now validate hashtable has properties we need: + // Author, Version, Guid, Description (but Description is already validated) + + if (!TryValidateScript( + parsedScriptMetadata: parsedScriptMetadata, + errors: out ErrorRecord[] validationErrors)) + { + errorsList.AddRange(validationErrors); + errors = errorsList.ToArray(); + return false; + } + + // at this point, we've parsed into hashtable AND validated we have all required properties for .ps1 + // now create instance of and populate PSScriptFileInfo + try + { + char[] spaceDelimeter = new char[]{' '}; + + Guid parsedGuid = new Guid((string) parsedScriptMetadata["GUID"]); + string parsedVersion = (string) parsedScriptMetadata["VERSION"]; + string parsedAuthor = (string) parsedScriptMetadata["AUTHOR"]; + string parsedDescription = (string) parsedScriptMetadata["DESCRIPTION"]; + + + string parsedCompanyName = (string) parsedScriptMetadata["COMPANYNAME"] ?? String.Empty; + string parsedCopyright = (string) parsedScriptMetadata["COPYRIGHT"] ?? String.Empty; + string parsedPrivateData = (string) parsedScriptMetadata["PRIVATEDATA"] ?? String.Empty; + string parsedReleaseNotes = (string) parsedScriptMetadata["RELEASENOTES"] ?? String.Empty; + + string[] parsedTags = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["TAGS"]); + string[] parsedExternalModuleDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["EXTERNALMODULEDEPENDENCIES"]); + string[] parsedRequiredScripts = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["REQUIREDSCRIPTS"]); + string[] parsedExternalScriptDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["EXTERNALSCRIPTDEPENDENCIES"]); + + ReadOnlyCollection parsedModules = (ReadOnlyCollection) parsedScriptMetadata["REQUIREDMODULES"] ?? + new ReadOnlyCollection(new List()); + + ModuleSpecification[] parsedModulesArray = parsedModules.Count() == 0 ? new ModuleSpecification[]{} : parsedModules.ToArray(); + + Uri parsedLicenseUri = null; + if (!String.IsNullOrEmpty((string) parsedScriptMetadata["LICENSEURI"])) + { + if (!Uri.TryCreate((string) parsedScriptMetadata["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri)) + { + verboseMsgsList.Add($"LicenseUri property {(string) parsedScriptMetadata["LICENSEURI"]} could not be created as a Uri"); + } + } + + Uri parsedProjectUri = null; + if (!String.IsNullOrEmpty((string) parsedScriptMetadata["PROJECTURI"])) + { + if (!Uri.TryCreate((string) parsedScriptMetadata["PROJECTURI"], UriKind.Absolute, out parsedProjectUri)) + { + verboseMsgsList.Add($"ProjectUri property {(string) parsedScriptMetadata["PROJECTURI"]} could not be created as Uri"); + } + } + + Uri parsedIconUri = null; + if (!String.IsNullOrEmpty((string) parsedScriptMetadata["ICONURI"])) + { + if (!Uri.TryCreate((string) parsedScriptMetadata["ICONURI"], UriKind.Absolute, out parsedIconUri)) + { + verboseMsgsList.Add($"IconUri property {(string) parsedScriptMetadata["ICONURI"]} could not be created as Uri"); + } + } + + // parsedScriptMetadata should contain all keys, but values may be empty (i.e empty array, String.empty) + parsedScript = new PSScriptFileInfo( + version: parsedVersion, + guid: parsedGuid, + author: parsedAuthor, + companyName: parsedCompanyName, + copyright: parsedCopyright, + tags: parsedTags, + licenseUri: parsedLicenseUri, + projectUri: parsedProjectUri, + iconUri: parsedIconUri, + requiredModules: parsedModulesArray, + externalModuleDependencies: parsedExternalModuleDependencies, + requiredScripts: parsedRequiredScripts, + externalScriptDependencies: parsedExternalScriptDependencies, + releaseNotes: parsedReleaseNotes, + privateData: parsedPrivateData, + description: parsedDescription, + endOfFileContents: endofFileContents); + } + 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, "PSScriptFileInfoObjectNotCreatedFromFile", ErrorCategory.ParserError, null); + errorsList.Add(PSScriptFileInfoObjectNotCreatedFromFileError); + errors = errorsList.ToArray(); + return false; + } + + return true; + } + + /// + /// Updates the contents of the .ps1 file at the provided path with the properties provided + /// and writes new updated script file contents to a string and updates the original PSScriptFileInfo object. + /// + internal static bool TryUpdateScriptFileContents( + PSScriptFileInfo scriptInfo, + out string updatedPSScriptFileContents, + out ErrorRecord[] errors, + string version, + Guid guid, + string author, + string companyName, + string copyright, + string[] tags, + Uri licenseUri, + Uri projectUri, + Uri iconUri, + ModuleSpecification[] requiredModules, + string[] externalModuleDependencies, + string[] requiredScripts, + string[] externalScriptDependencies, + string releaseNotes, + string privateData, + string description) + { + updatedPSScriptFileContents = String.Empty; + errors = new ErrorRecord[]{}; + List errorsList = new List(); + + if (scriptInfo == null) + { + throw new ArgumentNullException(nameof(scriptInfo)); + } + + // create new PSScriptFileInfo with updated fields + 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); + errorsList.Add(versionParseIntoNuGetVersionError); + errors = errorsList.ToArray(); + return false; + } + scriptInfo.Version = updatedVersion; + } + + if (guid != Guid.Empty) + { + scriptInfo.Guid = guid; + } + + if (!String.IsNullOrEmpty(author)) + { + scriptInfo.Author = author; + } + + if (!String.IsNullOrEmpty(companyName)){ + scriptInfo.CompanyName = companyName; + } + + if (!String.IsNullOrEmpty(copyright)){ + scriptInfo.Copyright = copyright; + } + + if (tags != null && tags.Length != 0){ + scriptInfo.Tags = tags; + } + + if (licenseUri != null && !licenseUri.Equals(default(Uri))){ + scriptInfo.LicenseUri = licenseUri; + } + + if (projectUri != null && !projectUri.Equals(default(Uri))){ + scriptInfo.ProjectUri = projectUri; + } + + if (iconUri != null && !iconUri.Equals(default(Uri))){ + scriptInfo.IconUri = iconUri; + } + + if (requiredModules != null && requiredModules.Length != 0){ + scriptInfo.RequiredModules = requiredModules; + } + + if (externalModuleDependencies != null && externalModuleDependencies.Length != 0){ + scriptInfo.ExternalModuleDependencies = externalModuleDependencies; + } + + if (requiredScripts != null && requiredScripts.Length != 0) + { + scriptInfo.RequiredScripts = requiredScripts; + } + + if (externalScriptDependencies != null && externalScriptDependencies.Length != 0){ + scriptInfo.ExternalScriptDependencies = externalScriptDependencies; + } + + if (!String.IsNullOrEmpty(releaseNotes)) + { + scriptInfo.ReleaseNotes = releaseNotes; + } + + if (!String.IsNullOrEmpty(privateData)) + { + scriptInfo.PrivateData = privateData; + } + + if (!String.IsNullOrEmpty(description)) + { + scriptInfo.Description = description; + } + + // create string contents for .ps1 file + if (!scriptInfo.TryCreateScriptFileInfoString( + pSScriptFileString: out string psScriptFileContents, + errors: out ErrorRecord[] createFileContentErrors)) + { + errorsList.AddRange(createFileContentErrors); + errors = errorsList.ToArray(); + return false; + } + + updatedPSScriptFileContents = psScriptFileContents; + return true; + } + + #endregion + + #region Public Methods + + /// + /// Create .ps1 file contents as a string from PSScriptFileInfo object's properties + /// end of file contents are not yet added to the string contents of the file. + /// + internal bool TryCreateScriptFileInfoString( + out string pSScriptFileString, // this is the string with the contents we want to put in the new ps1 file + out ErrorRecord[] errors) + { + errors = new ErrorRecord[]{}; + List errorsList = new List(); + + pSScriptFileString = String.Empty; + bool fileContentsSuccessfullyCreated = false; + + // this string/block is required + // this can only have one error (i.e Author or Version is missing) + if (!GetPSScriptInfoString( + pSScriptInfoString: out string psScriptInfoCommentString, + out ErrorRecord[] scriptInfoErrors)) + { + if (scriptInfoErrors.Length != 0) + { + errors = scriptInfoErrors.ToArray(); + } + + return fileContentsSuccessfullyCreated; + } + + pSScriptFileString = psScriptInfoCommentString; + + // populating this block is not required to fulfill .ps1 script requirements. + // this won't report any errors. + GetRequiresString(psRequiresString: out string psRequiresString); + if (!String.IsNullOrEmpty(psRequiresString)) + { + pSScriptFileString += "\n"; + pSScriptFileString += psRequiresString; + } + + // this string/block will contain Description, which is required + // this can only have one error (i.e Description is missing) + if (!GetScriptCommentHelpInfo( + psHelpInfo: out string psHelpInfo, + error: out ErrorRecord commentHelpInfoError)) + { + if (commentHelpInfoError != null) + { + errorsList.Add(commentHelpInfoError); + errors = errorsList.ToArray(); + } + + pSScriptFileString = String.Empty; + return fileContentsSuccessfullyCreated; + } + + pSScriptFileString += "\n"; // need a newline after last #> and before <# for script comment block + // or else not recongnized as a valid comment help info block when parsing the created ps1 later + pSScriptFileString += "\n" + psHelpInfo; + + // at this point either: + // have a new script being created without endOfFileContents, or + // have a script being updated, and contains no Signature, or contains a Signature but -RemoveSignature was used with cmdlet + if (!String.IsNullOrEmpty(EndOfFileContents)) + { + RemoveSignatureString(); + pSScriptFileString += "\n" + EndOfFileContents; + } + + fileContentsSuccessfullyCreated = true; + return fileContentsSuccessfullyCreated; + } + + #endregion + + #region Private Methods + + /// + /// Used when creating .ps1 file's contents. + /// This creates the <#PSScriptInfo ... #> comment string. + /// + private bool GetPSScriptInfoString( + out string pSScriptInfoString, + out ErrorRecord[] errors) + { + List errorsList = new List(); + bool pSScriptInfoSuccessfullyCreated = false; + pSScriptInfoString = String.Empty; + + /** + 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 + #> + */ + + if (String.IsNullOrEmpty(Author)) + { + var exMessage = "Author is missing when creating PSScriptComment info block"; + var ex = new ArgumentException(exMessage); + var authorMissingWhenCreatingPSScriptCommentError = new ErrorRecord(ex, "authorMissingWhenCreatingPSScriptComment", ErrorCategory.InvalidArgument, null); + errorsList.Add(authorMissingWhenCreatingPSScriptCommentError); + pSScriptInfoSuccessfullyCreated = false; + } + + if (String.IsNullOrEmpty(Author)) + { + var exMessage = "Version is missing when creating PSScriptComment info block"; + var ex = new ArgumentException(exMessage); + var versionMissingWhenCreatingPSScriptCommentError = new ErrorRecord(ex, "versionMissingWhenCreatingPSScriptComment", ErrorCategory.InvalidArgument, null); + errorsList.Add(versionMissingWhenCreatingPSScriptCommentError); + pSScriptInfoSuccessfullyCreated = false; + } + + if (String.IsNullOrEmpty(Author)) + { + var exMessage = "Description is missing when creating PSScriptComment info block"; + var ex = new ArgumentException(exMessage); + var descriptionMissingWhenCreatingPSScriptCommentError = new ErrorRecord(ex, "descriptionMissingWhenCreatingPSScriptComment", ErrorCategory.InvalidArgument, null); + errorsList.Add(descriptionMissingWhenCreatingPSScriptCommentError); + pSScriptInfoSuccessfullyCreated = false; + } + + if (Guid == Guid.Empty) + { + var exMessage = "Guid is missing when creating PSScriptComment info block"; + var ex = new ArgumentException(exMessage); + var guidMissingWhenCreatingPSScriptCommentError = new ErrorRecord(ex, "guidMissingWhenCreatingPSScriptComment", ErrorCategory.InvalidArgument, null); + errorsList.Add(guidMissingWhenCreatingPSScriptCommentError); + pSScriptInfoSuccessfullyCreated = false; + } + + if (pSScriptInfoSuccessfullyCreated) + { + errors = errorsList.ToArray(); + return false; + } + + + List psScriptInfoLines = new List(); + + psScriptInfoLines.Add("<#PSScriptInfo"); + psScriptInfoLines.Add(String.Format(".VERSION {0}", Version.ToString())); + psScriptInfoLines.Add(String.Format(".GUID {0}", Guid.ToString())); + psScriptInfoLines.Add(String.Format(".AUTHOR {0}", Author)); + psScriptInfoLines.Add(String.Format(".COMPANYNAME {0}", CompanyName)); + psScriptInfoLines.Add(String.Format(".COPYRIGHT {0}", Copyright)); + psScriptInfoLines.Add(String.Format(".TAGS {0}", String.Join(" ", Tags))); + psScriptInfoLines.Add(String.Format(".LICENSEURI {0}", LicenseUri == null ? String.Empty : LicenseUri.ToString())); + psScriptInfoLines.Add(String.Format(".PROJECTURI {0}", ProjectUri == null ? String.Empty : ProjectUri.ToString())); + psScriptInfoLines.Add(String.Format(".ICONURI {0}", IconUri == null ? String.Empty : IconUri.ToString())); + psScriptInfoLines.Add(String.Format(".EXTERNALMODULEDEPENDENCIES {0}", String.Join(" ", ExternalModuleDependencies))); + psScriptInfoLines.Add(String.Format(".REQUIREDSCRIPTS {0}", String.Join(" ", RequiredScripts))); + psScriptInfoLines.Add(String.Format(".EXTERNALSCRIPTDEPENDENCIES {0}", String.Join(" ", ExternalScriptDependencies))); + psScriptInfoLines.Add(String.Format(".RELEASENOTES\n{0}", ReleaseNotes)); + psScriptInfoLines.Add(String.Format(".PRIVATEDATA\n{0}", PrivateData)); + psScriptInfoLines.Add("#>"); + + pSScriptInfoString = String.Join("\n\n", psScriptInfoLines); + errors = errorsList.ToArray(); + return true; + } + + /// + /// Used when creating .ps1 file's contents. + /// This creates the #Requires comment string. + /// + private void GetRequiresString(out string psRequiresString) + { + psRequiresString = String.Empty; + + if (RequiredModules.Length > 0) + { + List psRequiresLines = new List(); + psRequiresLines.Add("\n"); + foreach (ModuleSpecification moduleSpec in RequiredModules) + { + psRequiresLines.Add(String.Format("#Requires -Module {0}", moduleSpec.ToString())); + } + + psRequiresLines.Add("\n"); + psRequiresString = String.Join("\n", psRequiresLines); + } + } + + /// + /// Used when creating .ps1 file's contents. + /// This creates the help comment string: <# \n .DESCRIPTION{mydescription}\n\n#> + /// + private bool GetScriptCommentHelpInfo( + out string psHelpInfo, + out ErrorRecord error) + { + error = null; + psHelpInfo = String.Empty; + bool psHelpInfoSuccessfullyCreated = false; + List psHelpInfoLines = new List(); + + 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 psHelpInfoSuccessfullyCreated; + } + + 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 psHelpInfoSuccessfullyCreated; + } + + psHelpInfoSuccessfullyCreated = true; + psHelpInfoLines.Add("<#\n"); + psHelpInfoLines.Add(String.Format(".DESCRIPTION\n{0}", Description)); + + if (!String.IsNullOrEmpty(Synopsis)) + { + psHelpInfoLines.Add(String.Format(".SYNOPSIS\n{0}", Synopsis)); + } + + foreach (string currentExample in Example) + { + psHelpInfoLines.Add(String.Format(".EXAMPLE\n{0}", currentExample)); + } + + foreach (string input in Inputs) + { + psHelpInfoLines.Add(String.Format(".INPUTS\n{0}", input)); + } + + foreach (string output in Outputs) + { + psHelpInfoLines.Add(String.Format(".OUTPUTS\n{0}", output)); + } + + if (Notes.Length > 0) + { + psHelpInfoLines.Add(String.Format(".NOTES\n{0}", String.Join("\n", Notes))); + } + + foreach (string link in Links) + { + psHelpInfoLines.Add(String.Format(".LINK\n{0}", link)); + } + + if (Component.Length > 0) + { + psHelpInfoLines.Add(String.Format(".COMPONENT\n{0}", String.Join("\n", Component))); + } + + if (Role.Length > 0) + { + psHelpInfoLines.Add(String.Format(".ROLE\n{0}", String.Join("\n", Role))); + } + + if (Functionality.Length > 0) + { + psHelpInfoLines.Add(String.Format(".FUNCTIONALITY\n{0}", String.Join("\n", Functionality))); + } + + psHelpInfo = String.Join("\n", psHelpInfoLines); + psHelpInfo = psHelpInfo.TrimEnd('\n'); + psHelpInfo += "\n\n#>"; + return psHelpInfoSuccessfullyCreated; + } + + /// + /// Helper method which takes lines of the PSScriptInfo comment block + /// and parses metadata from those lines into a hashtable. + /// + private static void GetMetadataFromCommentLines( + string[] commentLines, + ref Hashtable parsedScriptMetadata) + { + // parsedScriptMetadata = 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)) + { + parsedScriptMetadata.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 (!(line.Trim()).Equals("#>")) + { + // TODO: this condition won't be hit anymore, as we don't add #> to array of comment lines captured + // 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; + } + } + else + { + // scenario where line is: #> + // this line signifies end of comment block, so add last recorded key value pair before the comment block ends. + if (!String.IsNullOrEmpty(keyName)) + { + parsedScriptMetadata.Add(keyName, value); + } + } + } + + } + + /// + /// Ensure no fields (passed as stringToValidate) contains '<#' or '#>' (would break AST block section). + /// + private bool StringContainsComment(string stringToValidate) + { + return stringToValidate.Contains("<#") || stringToValidate.Contains("#>"); + } + + /// + /// Removes the signature from the current PSScriptFileInfo instance's EndOfFileContents property + /// as the signature would be invalidated during update. + /// + private void RemoveSignatureString() + { + if (EndOfFileContents.Contains(signatureStartString)) + { + int signatureStartIndex = EndOfFileContents.IndexOf(signatureStartString); + EndOfFileContents = EndOfFileContents.Substring(0, signatureStartIndex); + } + } + + #endregion + + } +} diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs new file mode 100644 index 000000000..09a1448bb --- /dev/null +++ b/src/code/TestPSScriptFileInfo.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Management.Automation; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// Tests the contents of a .ps1 file to see if it has all properties and is in correct format + /// for publishing the script with the file. + /// + [Cmdlet(VerbsDiagnostic.Test, "PSScriptFileInfo")] + [OutputType(typeof(bool))] + public sealed class TestPSScriptFileInfo : PSCmdlet + { + #region Parameters + + /// + /// The path to the .ps1 file to test. + /// + [Parameter(Position = 0, Mandatory = true)] + [ValidateNotNullOrEmpty] + public string FilePath { get; set; } + + #endregion + + #region Methods + + protected override void EndProcessing() + { + if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + { + var exMessage = "Path needs to end with a .ps1 file. Example: C:/Users/john/x/MyScript.ps1"; + var ex = new ArgumentException(exMessage); + var InvalidPathError = new ErrorRecord(ex, "InvalidPath", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InvalidPathError); + } + + var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); + if (resolvedPaths.Count != 1) + { + var exMessage = "Error: Could not resolve provided Path argument into a single path."; + var ex = new PSArgumentException(exMessage); + var InvalidPathArgumentError = new ErrorRecord(ex, "InvalidPathArgumentError", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InvalidPathArgumentError); + } + + var resolvedFilePath = resolvedPaths[0].Path; + + if (!File.Exists(resolvedFilePath)) + { + var exMessage = "A 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.TryParseScriptIntoPSScriptInfo( + scriptFileInfoPath: resolvedFilePath, + parsedScript: out PSScriptFileInfo parsedScriptInfo, + errors: out ErrorRecord[] errors, + out string[] verboseMsgs); + + if (!isValidScript) + { + foreach (ErrorRecord error in errors) + { + WriteWarning("The .ps1 script file passed in was not valid due to: " + error.Exception.Message); + } + } + + foreach (string msg in verboseMsgs) + { + WriteVerbose(msg); + + // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. + WriteWarning(msg); + } + + WriteObject(isValidScript); + } + + #endregion + } +} diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs new file mode 100644 index 000000000..6db705283 --- /dev/null +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -0,0 +1,322 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.IO; +using System.Management.Automation; +using Microsoft.PowerShell.Commands; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// Updates a .ps1 file with specified properties. + /// + [Cmdlet(VerbsData.Update, "PSScriptFileInfo")] + public sealed class UpdatePSScriptFileInfo : PSCmdlet + { + #region Parameters + + /// + /// The author of the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Author { get; set; } + + /// + /// The name of the company owning the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string CompanyName { get; set; } + + /// + /// The copyright statement for the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Copyright { get; set; } + + /// + /// The description of the script. + /// + [Parameter()] + [ValidateNotNullOrEmpty()] + public string Description { get; set; } + + /// + /// The list of external module dependencies taken by this script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] ExternalModuleDependencies { get; set; } + + /// + /// The list of external script dependencies taken by this script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] ExternalScriptDependencies { get; set; } + + /// + /// The unique identifier for the script. The GUID can be used to distinguish among scripts with the same name. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public Guid Guid { get; set; } + + /// + /// The Uri for the icon associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string IconUri { get; set; } + + /// + /// The Uri for the license associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string LicenseUri { get; set; } + + /// + /// The path the .ps1 script info file will be created at. + /// + [Parameter(Position = 0, Mandatory = true)] + [ValidateNotNullOrEmpty] + public string FilePath { get; set; } + + /// + /// The private data associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string PrivateData { get; set; } + + /// + /// The Uri for the project associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string ProjectUri { get; set; } + + /// + /// The release notes for the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string ReleaseNotes { get; set; } + + /// + /// Remove signature from signed .ps1 (if present) thereby allowing update of script to happen + /// User should re-sign the updated script afterwards. + /// + [Parameter] + public SwitchParameter RemoveSignature { get; set; } + + /// + /// The list of modules required by the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public Hashtable[] RequiredModules { get; set; } + + /// + /// The list of scripts required by the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] RequiredScripts { get; set; } + + /// + /// The tags associated with the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] Tags { get; set; } + + /// + /// The version of the script. + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Version { get; set; } + + #endregion + + #region Private Members + + private const string signatureStartString = "# SIG # Begin signature block"; + + #endregion + + #region Methods + + protected override void EndProcessing() + { + Uri projectUri = null; + if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri, + cmdletPassedIn: this, + uriResult: out projectUri, + errorRecord: out ErrorRecord projectErrorRecord)) + { + ThrowTerminatingError(projectErrorRecord); + } + + Uri licenseUri = null; + if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUri(uriString: LicenseUri, + cmdletPassedIn: this, + uriResult: out licenseUri, + errorRecord: out ErrorRecord licenseErrorRecord)) + { + ThrowTerminatingError(licenseErrorRecord); + } + + Uri iconUri = null; + if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUri(uriString: IconUri, + cmdletPassedIn: this, + uriResult: out iconUri, + errorRecord: out ErrorRecord iconErrorRecord)) + { + ThrowTerminatingError(iconErrorRecord); + } + + if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + { + var exMessage = "File path needs to end with a .ps1 extension. Example: C:/Users/john/x/MyScript.ps1"; + var ex = new ArgumentException(exMessage); + var InvalidOrNonExistantPathError = new ErrorRecord(ex, "InvalidOrNonExistantPath", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InvalidOrNonExistantPathError); + } + + var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); + if (resolvedPaths.Count != 1) + { + var exMessage = "Error: Could not resolve provided Path argument into a single path."; + var ex = new PSArgumentException(exMessage); + var InvalidPathArgumentError = new ErrorRecord(ex, "InvalidPathArgumentError", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InvalidPathArgumentError); + } + + string resolvedFilePath = resolvedPaths[0].Path; + + if (!File.Exists(resolvedFilePath)) + { + var exMessage = "A script file does not exist at the location specified"; + var ex = new ArgumentException(exMessage); + var FileDoesNotExistError = new ErrorRecord(ex, "FileDoesNotExistAtPath", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(FileDoesNotExistError); + } + + ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; + if (RequiredModules != null && RequiredModules.Length > 0) + { + if (!Utils.TryCreateModuleSpecification( + moduleSpecHashtables: RequiredModules, + out validatedRequiredModuleSpecifications, + out ErrorRecord[] moduleSpecErrors)) + { + foreach (ErrorRecord err in moduleSpecErrors) + { + WriteError(err); + } + + return; + } + } + + if (!PSScriptFileInfo.TryParseScriptIntoPSScriptInfo( + scriptFileInfoPath: resolvedFilePath, + parsedScript: out PSScriptFileInfo parsedScriptInfo, + errors: out ErrorRecord[] errors, + out string[] verboseMsgs)) + { + foreach (string msg in verboseMsgs) + { + WriteVerbose(msg); + + // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. + WriteWarning(msg); + } + + WriteWarning("The .ps1 script file passed in was not valid due to the following error(s) listed below"); + foreach (ErrorRecord error in errors) + { + WriteError(error); + } + + return; + } + + if (parsedScriptInfo.EndOfFileContents.Contains(signatureStartString)) + { + WriteWarning("This script contains a signature and cannot be updated without invalidating the current script signature"); + if (!RemoveSignature) + { + var exMessage = "Cannot update the script file because the file contains a signature block and updating will invalidate the signature. Use -RemoveSignature to remove the signature block, and then re-sign the file after it is updated."; + var ex = new PSInvalidOperationException(exMessage); + var ScriptToBeUpdatedContainsSignatureError = new ErrorRecord(ex, "ScriptToBeUpdatedContainsSignature", ErrorCategory.InvalidOperation, null); + ThrowTerminatingError(ScriptToBeUpdatedContainsSignatureError); + } + } + + if (!PSScriptFileInfo.TryUpdateScriptFileContents( + scriptInfo: parsedScriptInfo, + updatedPSScriptFileContents: out string updatedPSScriptFileContents, + errors: out ErrorRecord[] updateErrors, + version: Version, + guid: Guid, + author: Author, + companyName: CompanyName, + copyright: Copyright, + tags: Tags, + licenseUri: licenseUri, + projectUri: projectUri, + iconUri: iconUri, + requiredModules: validatedRequiredModuleSpecifications, + externalModuleDependencies: ExternalModuleDependencies, + requiredScripts: RequiredScripts, + externalScriptDependencies: ExternalScriptDependencies, + releaseNotes: ReleaseNotes, + privateData: PrivateData, + description: Description)) + { + WriteWarning("Updating the specified script file failed due to the following error(s):"); + foreach (ErrorRecord error in updateErrors) + { + WriteError(error); + } + + return; + } + + string tempScriptFilePath = null; + try + { + tempScriptFilePath = Path.GetTempFileName(); + + File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); + File.Copy(tempScriptFilePath, resolvedFilePath, overwrite: true); + } + catch(Exception e) + { + WriteError(new ErrorRecord( + new PSInvalidOperationException($"Could not update .ps1 file due to: {e.Message}"), + "FileIOErrorDuringUpdate", + ErrorCategory.InvalidArgument, + this)); + } + finally + { + if (tempScriptFilePath != null) + { + File.Delete(tempScriptFilePath); + } + } + } + + #endregion + } +} diff --git a/src/code/Utils.cs b/src/code/Utils.cs index af3687a1f..cd42d4f7c 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -15,6 +15,8 @@ using System.Management.Automation.Runspaces; using System.Runtime.InteropServices; using System.Security; +using System.Text.RegularExpressions; +using Microsoft.PowerShell.Commands; using System.Security.Cryptography.X509Certificates; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses @@ -113,6 +115,17 @@ public static string[] GetStringArray(ArrayList list) return strArray; } + public static string[] GetStringArrayFromString(char[] delimeter, string stringToConvertToArray) + { + // this will be a string where entries are separated by space + if (String.IsNullOrEmpty(stringToConvertToArray)) + { + return Utils.EmptyStrArray; + } + + return stringToConvertToArray.Split(delimeter, StringSplitOptions.RemoveEmptyEntries); + } + public static string[] ProcessNameWildcards( string[] pkgNames, out string[] errorMsgs, @@ -857,6 +870,117 @@ public static Hashtable ConvertJsonToHashtable( return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; } + public static bool TryCreateModuleSpecification( + Hashtable[] moduleSpecHashtables, + out ModuleSpecification[] validatedModuleSpecs, + out ErrorRecord[] errors) + { + bool moduleSpecCreatedSuccessfully = true; + List errorList = new List(); + validatedModuleSpecs = new ModuleSpecification[]{}; + List moduleSpecsList = new List(); + + foreach(Hashtable moduleSpec in moduleSpecHashtables) + { + // ModuleSpecification(string) constructor for creating a ModuleSpecification when only ModuleName is provided + if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) + { + var exMessage = $"RequiredModules Hashtable entry {moduleSpec.ToString()} is missing a key 'ModuleName' and associated value, which is required for each module specification entry"; + var ex = new ArgumentException(exMessage); + var NameMissingModuleSpecError = new ErrorRecord(ex, "NameMissingInModuleSpecification", ErrorCategory.InvalidArgument, null); + errorList.Add(NameMissingModuleSpecError); + moduleSpecCreatedSuccessfully = false; + continue; + } + + // at this point it must contain ModuleName key. + string moduleSpecName = (string) moduleSpec["ModuleName"]; + ModuleSpecification currentModuleSpec = null; + if (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion")) + { + // pass to ModuleSpecification(string) constructor + // this constructor method would only throw for a null/empty string, which we've already validated against above + currentModuleSpec = new ModuleSpecification(moduleSpecName); + + if (currentModuleSpec != null) + { + moduleSpecsList.Add(currentModuleSpec); + } + else + { + var exMessage = $"ModuleSpecification object was not able to be created for {moduleSpecName}"; + var ex = new ArgumentException(exMessage); + var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + errorList.Add(ModuleSpecNotCreatedError); + moduleSpecCreatedSuccessfully = false; + continue; + } + } + else + { + // ModuleSpecification(Hashtable) constructor for when ModuleName + {Required,Maximum,Module}Version value is also provided + string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaximumVersion"] : String.Empty; + string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; + string moduleSpecRequiredVersion = moduleSpec.ContainsKey("RequiredVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; + Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; + + if (String.IsNullOrEmpty(moduleSpecMaxVersion) && String.IsNullOrEmpty(moduleSpecModuleVersion) && String.IsNullOrEmpty(moduleSpecRequiredVersion)) + { + var exMessage = $"ModuleSpecification hashtable requires one of the following keys: MaximumVersion, ModuleVersion, RequiredVersion and failed to be created for {moduleSpecName}"; + var ex = new ArgumentException(exMessage); + var MissingModuleSpecificationMemberError = new ErrorRecord(ex, "MissingModuleSpecificationMember", ErrorCategory.InvalidArgument, null); + errorList.Add(MissingModuleSpecificationMemberError); + moduleSpecCreatedSuccessfully = false; + continue; + } + + Hashtable moduleSpecHash = new Hashtable(); + + moduleSpecHash.Add("ModuleName", moduleSpecName); + if (moduleSpecGuid != Guid.Empty) + { + moduleSpecHash.Add("Guid", moduleSpecGuid); + } + + if (!String.IsNullOrEmpty(moduleSpecMaxVersion)) + { + moduleSpecHash.Add("MaximumVersion", moduleSpecMaxVersion); + } + + if (!String.IsNullOrEmpty(moduleSpecModuleVersion)) + { + moduleSpecHash.Add("ModuleVersion", moduleSpecModuleVersion); + } + + if (!String.IsNullOrEmpty(moduleSpecRequiredVersion)) + { + moduleSpecHash.Add("RequiredVersion", moduleSpecRequiredVersion); + } + + try + { + currentModuleSpec = new ModuleSpecification(moduleSpecHash); + } + catch (Exception e) + { + var ex = new ArgumentException($"ModuleSpecification instance was not able to be created with hashtable constructor due to: {e.Message}"); + var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + errorList.Add(ModuleSpecNotCreatedError); + moduleSpecCreatedSuccessfully = false; + } + + if (currentModuleSpec != null) + { + moduleSpecsList.Add(currentModuleSpec); + } + } + } + + errors = errorList.ToArray(); + validatedModuleSpecs = moduleSpecsList.ToArray(); + return moduleSpecCreatedSuccessfully; + } + #endregion #region Directory and File diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index 3ba34acf3..727a05792 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -1,299 +1,241 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force - -Describe "Test New-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 "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 - } - - 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 - - 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" - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -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 - } - - It "Create new .ps1 given Guid parameter" { - $Guid = [guid]::NewGuid() - $Description = "Test description" - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -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 - } - - It "Create new .ps1 given Author parameter" { - $Author = "Test Author" - $Description = "Test description" - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -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 - } - - 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 - } - - It "Create new .ps1 given CompanyName parameter" { - $CompanyName = "Microsoft" - $Description = "Test description" - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -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 - } - - It "Create new .ps1 given Copyright parameter" { - $Copyright = "(c) Test Corporation" - $Description = "Test description" - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -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 - } - - It "Create new .ps1 given RequiredModules parameter" { - $requiredModuleName = 'PackageManagement' - $requiredModuleVersion = '1.0.0.0' - $RequiredModules = @(@{ModuleName = $requiredModuleName; ModuleVersion = $requiredModuleVersion }) - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredModules $RequiredModules -Description $Description - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($requiredModuleName) | Should -BeTrue - $results.Contains($requiredModuleVersion) | Should -BeTrue - $results -like ".REQUIREDMODULES*$requiredModuleName*$requiredModuleVersion" | Should -BeTrue - } - - It "Create new .ps1 given ReleaseNotes parameter" { - $Description = "Test Description" - $ReleaseNotes = "Release notes for script." - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -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 - } - - It "Create new .ps1 given Tags parameter" { - $Description = "Test Description" - $Tag1 = "tag1" - $Tag2 = "tag2" - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -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 - } - - 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 - } - - It "Create new .ps1 given LicenseUri parameter" { - $Description = "Test Description" - $LicenseUri = "https://www.testlicenseuri.com/" - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -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 - } - - 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 - } - - 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 - } - - 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 - - $null = New-Item -Path $RequiredAssemblyPath1 -ItemType File -Force - $null = New-Item -Path $RequiredAssemblyPath2 -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredAssemblies $RequiredAssembly1, $RequiredAssembly2 -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 - } - - 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 - } - - 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 - } - - 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 - } -#> -} \ No newline at end of file +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force + +Describe "Test New-PSScriptFileInfo" { + BeforeAll { + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDirPaths = @($tmpDir1Path) + Get-NewTestDirs($tmpDirPaths) + } + BeforeEach { + $script:PSScriptInfoName = "test_script" + $script:testScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "$script:PSScriptInfoName.ps1" + } + AfterEach { + if (Test-Path -Path $script:testScriptFilePath) + { + Remove-Item $script:testScriptFilePath + } + } + + It "Create .ps1 file with minimal required fields" { + $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 = "Test description" + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $description + + 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" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Version $version -Description $description + + 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" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Guid $guid -Description $description + + 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" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Author $author -Description $description + + 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:testScriptFilePath -Description $description + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($description) | Should -BeTrue + $results -like "*.DESCRIPTION`n*$description*" | Should -BeTrue + } + + It "Create new .ps1 given CompanyName parameter" { + $companyName = "Microsoft" + $description = "Test description" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -CompanyName $companyName -Description $description + + 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" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Copyright $copyright -Description $description + + 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" { + $requiredModuleName = 'PackageManagement' + $requiredModuleVersion = '1.0.0.0' + $RequiredModules = @(@{ModuleName = $requiredModuleName; ModuleVersion = $requiredModuleVersion }) + + $description = "Test description" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -RequiredModules $RequiredModules -Description $Description + + 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 "*#Requires*$requiredModuleName*$requiredModuleVersion*" | Should -BeTrue + } + + It "Create new .ps1 given ReleaseNotes parameter" { + $description = "Test Description" + $releaseNotes = "Release notes for script." + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -ReleaseNotes $releaseNotes -Description $description + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($releaseNotes) | Should -BeTrue + $results -like "*.RELEASENOTES`n*$ReleaseNotes*" | Should -BeTrue + } + + It "Create new .ps1 given Tags parameter" { + $description = "Test Description" + $tag1 = "tag1" + $tag2 = "tag2" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Tags $tag1, $tag2 -Description $description + + 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: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/" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -LicenseUri $licenseUri -Description $description + + 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:testScriptFilePath -IconUri $iconUri -Description $description + + 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 "Create new .ps1 given ExternalModuleDependencies parameter" { + $description = "Test Description" + $externalModuleDep1 = "ExternalModuleDep1" + $externalModuleDep2 = "ExternalModuleDep2" + + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -ExternalModuleDependencies $externalModuleDep1, $externalModuleDep2 -Description $description + + 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 = "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" + $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: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 + } +} diff --git a/test/PSGetTestUtils.psm1 b/test/PSGetTestUtils.psm1 index e72984f60..c83d6b305 100644 --- a/test/PSGetTestUtils.psm1 +++ b/test/PSGetTestUtils.psm1 @@ -178,7 +178,7 @@ function Get-RemoveTestDirs { { if(Test-Path -Path $path) { - Remove-Item -Path $path -Force -ErrorAction Ignore + Remove-Item -Path $path -Force -Recurse -ErrorAction Ignore } } } 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..91d59bbcd --- /dev/null +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -0,0 +1,312 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force + +Describe "Test Update-PSScriptFileInfo" { + BeforeAll { + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDirPaths = @($tmpDir1Path) + Get-NewTestDirs($tmpDirPaths) + + # Path to folder, within our test folder, where we store invalid module and script files used for testing + $script:testFilesFolderPath = Join-Path $psscriptroot -ChildPath "testFiles" + + # Path to specifically to that invalid test scripts folder + $script:testScriptsFolderPath = Join-Path $testFilesFolderPath -ChildPath "testScripts" + } + + BeforeEach { + $script:psScriptInfoName = "test_script" + $scriptDescription = "this is a test script" + $script:testScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "$script:psScriptInfoName.ps1" + New-PSScriptFileInfo -FilePath $script:testScriptFilePath -Description $scriptDescription + } + + AfterEach { + if (Test-Path -Path $script:testScriptFilePath) + { + Remove-Item $script:testScriptFilePath + } + } + + It "Update .ps1 file with relative path" { + $relativeCurrentPath = Get-Location + $scriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "$script:psScriptInfoName.ps1" + $oldDescription = "Old description for test script" + $newDescription = "New description for test script" + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $oldDescription + + Update-PSScriptFileInfo -FilePath $scriptFilePath -Description $newDescription + Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue + + Test-Path -Path $scriptFilePath | Should -BeTrue + $results = Get-Content -Path $scriptFilePath -Raw + $results.Contains($newDescription) | Should -BeTrue + $results -like "*.DESCRIPTION`n*$newDescription*" | Should -BeTrue + + Remove-Item -Path $scriptFilePath -Force + } + + It "Update script should not overwrite old script data unless that property is specified" { + $description = "Test Description" + $version = "3.0.0" + $author = "John Doe" + $newAuthor = "Jane Doe" + $projectUri = "https://testscript.com/" + + $relativeCurrentPath = Get-Location + $scriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "$script:psScriptInfoName.ps1" + + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $description -Version $version -Author $author -ProjectUri $projectUri + Update-PSScriptFileInfo -FilePath $scriptFilePath -Author $newAuthor + + Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue + $results = Get-Content -Path $scriptFilePath -Raw + $results.Contains($newAuthor) | Should -BeTrue + $results.Contains(".AUTHOR $newAuthor") | Should -BeTrue + + # rest should be original data used when creating the script + $results.Contains($projectUri) | Should -BeTrue + $results.Contains(".PROJECTURI $projectUri") | Should -BeTrue + + $results.Contains($version) | Should -BeTrue + $results.Contains(".VERSION $version") | Should -BeTrue + + $results.Contains($description) | Should -BeTrue + $results -like "*.DESCRIPTION`n*$description*" | Should -BeTrue + + Remove-Item -Path $scriptFilePath -Force + } + + It "update script file Author property" { + $author = "New Author" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Author $author + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($author) | Should -BeTrue + $results.Contains(".AUTHOR $author") | Should -BeTrue + } + + It "update script file Version property" { + $version = "2.0.0.0" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Version $version + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($version) | Should -BeTrue + $results.Contains(".VERSION $version") | Should -BeTrue + } + + It "update script file Version property with prerelease version" { + $version = "3.0.0-alpha" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Version $version + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($version) | Should -BeTrue + $results.Contains(".VERSION $version") | Should -BeTrue + } + + It "not update script file with invalid version" { + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Version "4.0.0.0.0" -ErrorVariable err -ErrorAction SilentlyContinue + $err.Count | Should -Not -Be 0 + $err[0].FullyQualifiedErrorId | Should -BeExactly "VersionParseIntoNuGetVersion,Microsoft.PowerShell.PowerShellGet.Cmdlets.UpdatePSScriptFileInfo" + } + + It "update script file Description property" { + $description = "this is an updated test script" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Description $description + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($description) | Should -BeTrue + $results -like "*.DESCRIPTION`n*$description*" | Should -BeTrue + } + + It "update script file Guid property" { + $guid = [Guid]::NewGuid(); + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Guid $guid + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($guid) | Should -BeTrue + $results.Contains(".GUID $guid") | Should -BeTrue + } + + It "update script file CompanyName property" { + $companyName = "New Corporation" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -CompanyName $companyName + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($companyName) | Should -BeTrue + $results.Contains(".COMPANYNAME $companyName") | Should -BeTrue + } + + It "update script file Copyright property" { + $copyright = "(c) 2022 New Corporation. All rights reserved" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Copyright $copyright + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($copyright) | Should -BeTrue + $results.Contains(".COPYRIGHT $copyright") | Should -BeTrue + } + + It "update script file ExternalModuleDependencies property" { + $externalModuleDep1 = "ExternalModuleDep1" + $externalModuleDep2 = "ExternalModuleDep2" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -ExternalModuleDependencies $externalModuleDep1,$externalModuleDep2 + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($externalModuleDep1) | Should -BeTrue + $results.Contains($externalModuleDep2) | Should -BeTrue + $results -like "*.EXTERNALMODULEDEPENDENCIES*$externalModuleDep1*$externalModuleDep2*" | Should -BeTrue + } + + It "update script file ExternalScriptDependencies property" { + $externalScriptDep1 = "ExternalScriptDep1" + $externalScriptDep2 = "ExternalScriptDep2" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -ExternalScriptDependencies $externalScriptDep1,$externalScriptDep2 + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($externalScriptDep1) | Should -BeTrue + $results.Contains($externalScriptDep2) | Should -BeTrue + $results -like "*.EXTERNALMODULEDEPENDENCIES*$externalScriptDep1*$externalScriptDep2*" | Should -BeTrue + } + + It "update script file IconUri property" { + $iconUri = "https://testscript.com/icon" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -IconUri $iconUri + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($iconUri) | Should -BeTrue + $results.Contains(".ICONURI $iconUri") | Should -BeTrue + } + + It "update script file LicenseUri property" { + $licenseUri = "https://testscript.com/license" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -LicenseUri $licenseUri + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($licenseUri) | Should -BeTrue + $results.Contains(".LICENSEURI $licenseUri") | Should -BeTrue + } + + It "update script file ProjectUri property" { + $projectUri = "https://testscript.com/" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -ProjectUri $projectUri + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($projectUri) | Should -BeTrue + $results.Contains(".PROJECTURI $projectUri") | Should -BeTrue + } + + It "update script file PrivateData property" { + $privateData = "this is some private data" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -PrivateData $privateData + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($privateData) | Should -BeTrue + $results -like "*.PRIVATEDATA*$privateData*" | Should -BeTrue + } + + It "update script file ReleaseNotes property" { + $releaseNotes = "Release notes for script." + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -ReleaseNotes $releaseNotes + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($releaseNotes) | Should -BeTrue + $results -like "*.RELEASENOTES`n*$releaseNotes*" | Should -BeTrue + } + + It "update script file RequiredModules property" { + $hashtable1 = @{ModuleName = "RequiredModule1"} + $hashtable2 = @{ModuleName = "RequiredModule2"; ModuleVersion = "1.0.0.0"} + $hashtable3 = @{ModuleName = "RequiredModule3"; RequiredVersion = "2.5.0.0"} + $hashtable4 = @{ModuleName = "RequiredModule4"; ModuleVersion = "1.1.0.0"; MaximumVersion = "2.0.0.0"} + $requiredModules = $hashtable1, $hashtable2, $hashtable3, $hashtable4 + + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -RequiredModules $requiredModules + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains("#Requires -Module RequiredModule1") | Should -BeTrue + $results -like "*#Requires*ModuleName*Version*" | Should -BeTrue + } + + It "update script file RequiredScripts property" { + $requiredScript1 = "RequiredScript1" + $requiredScript2 = "RequiredScript2" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -RequiredScripts $requiredScript1, $requiredScript2 + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($requiredScript1) | Should -BeTrue + $results.Contains($requiredScript2) | Should -BeTrue + $results -like "*.REQUIREDSCRIPTS*$requiredScript1*$requiredScript2*" | Should -BeTrue + } + + It "update script file Tags property" { + $tag1 = "tag1" + $tag2 = "tag2" + Update-PSScriptFileInfo -FilePath $script:testScriptFilePath -Tags $tag1, $tag2 + Test-PSScriptFileInfo $script:testScriptFilePath | Should -Be $true + + Test-Path -Path $script:testScriptFilePath | Should -BeTrue + $results = Get-Content -Path $script:testScriptFilePath -Raw + $results.Contains($tag1) | Should -BeTrue + $results.Contains($tag2) | Should -BeTrue + $results.Contains(".TAGS $tag1 $tag2") | Should -BeTrue + } + + It "throw error when attempting to update a signed script without -RemoveSignature parameter" { + # Note: user should sign the script again once it's been updated + + $scriptName = "ScriptWithSignature.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + # use a copy of the signed script file so we can re-use for other tests + $null = Copy-Item -Path $scriptFilePath -Destination $TestDrive + $tmpScriptFilePath = Join-Path -Path $TestDrive -ChildPath $scriptName + + { Update-PSScriptFileInfo -FilePath $tmpScriptFilePath -Version "2.0.0.0" } | Should -Throw -ErrorId "ScriptToBeUpdatedContainsSignature,Microsoft.PowerShell.PowerShellGet.Cmdlets.UpdatePSScriptFileInfo" + } + + It "update signed script when using RemoveSignature parameter" { + $scriptName = "ScriptWithSignature.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + # use a copy of the signed script file so we can re-use for other tests + $null = Copy-Item -Path $scriptFilePath -Destination $TestDrive + $tmpScriptFilePath = Join-Path -Path $TestDrive -ChildPath $scriptName + + Update-PSScriptFileInfo -FilePath $tmpScriptFilePath -Version "2.0.0.0" -RemoveSignature + Test-PSScriptFileInfo -FilePath $tmpScriptFilePath | Should -Be $true + } +} diff --git a/test/UpdatePSScriptInfo.Tests.ps1 b/test/UpdatePSScriptInfo.Tests.ps1 deleted file mode 100644 index eb1450eaf..000000000 --- a/test/UpdatePSScriptInfo.Tests.ps1 +++ /dev/null @@ -1,415 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force - -Describe "Test Update-PSScriptFileInfo" { - - BeforeAll { - $script:TempPath = Get-TempPath - } - BeforeEach { - # Create temp script path - $script:TempScriptPath = Join-Path $script:TempPath "PSGet_$(Get-Random)" - $null = New-Item -Path $script:TempScriptPath -ItemType Directory -Force - - $script:PSScriptInfoName = "PSGetTestScript" - $script:testPSScriptInfoPath = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath "$script:PSScriptInfoName.psd1" - } - AfterEach { - RemoveItem "$script:TempScriptPath" - } - - ### TODO: Add tests for -Force and -WhatIf if those parameters are applicable -<# - It "Update .ps1 file with relative path" { - $RelativeCurrentPath = Get-Location - $ScriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "$script:PSScriptInfoName.ps1" - $OldDescription = "Old description for test script" - $NewDescription = "Old description for test script" - New-PSScriptFileInfo -FilePath $ScriptFilePath -Description $OldDescription - - Update-PSScriptFileInfo -FilePath $ScriptFilePath -Description $NewDescription - - Test-PSScriptFileInfo -FilePath $ScriptFilePath | Should -BeTrue - Remove-Item -Path $ScriptFilePath - } - - It "Update .ps1 given Version parameter" { - $Version = "2.0.0.0" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Version $Version - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Version) | Should -BeTrue - $results.Contains(".VERSION $Version") | Should -BeTrue - } - - It "Update .ps1 given prerelease version" { - $Version = "2.0.0.0-alpha" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Version $Version - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Version) | Should -BeTrue - $results.Contains(".VERSION $Version") | Should -BeTrue - } - - It "Should not update .ps1 with invalid version" { - $Version = "4.0.0.0.0" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Version $Version -ErrorVariable err -ErrorAction SilentlyContinue - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeFalse - $err.Count | Should -Not -Be 0 - $err[0].FullyQualifiedErrorId | Should -BeExactly "VersionParseIntoNuGetVersion,Microsoft.PowerShell.PowerShellGet.Cmdlets.UpdatePSScriptFileInfo" - } - - It "Update .ps1 given Guid parameter" { - $Guid = [guid]::NewGuid() - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Guid $Guid - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Guid) | Should -BeTrue - $results.Contains(".GUID $Guid") | Should -BeTrue - } - - It "Update .ps1 given Author parameter" { - $Author = "New Author" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Author $Author - - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($Author) | Should -BeTrue - $results.Contains(".AUTHOR $Author") | Should -BeTrue - } - - It "Update .ps1 given Description parameter" { - $OldDescription = "Old description for test script." - $NewDescription = "New description for test script." - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $OldDescription - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Description $NewDescription - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewDescription) | Should -BeTrue - $results -like ".DESCRIPTION*$NewDescription" | Should -BeTrue - } - - It "Update .ps1 given CompanyName parameter" { - $OldCompanyName = "Old company name" - $NewCompanyName = "New company name" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -CompanyName $OldCompanyName -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -CompanyName $NewCompanyName - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($NewCompanyName) | Should -BeTrue - $results.Contains(".COMPANYNAME $NewCompanyName") | Should -BeTrue - } - - It "Update .ps1 given Copyright parameter" { - $OldCopyright = "(c) Old Test Corporation" - $NewCopyright = "(c) New Test Corporation" - $Description = "Test description" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Copyright $OldCopyright -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Copyright $NewCopyright - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testPSScriptInfoPath -Raw - $results.Contains($NewCopyright) | Should -BeTrue - $results.Contains(".COPYRIGHT $NewCopyright") | Should -BeTrue - } - - It "Update .ps1 given RequiredModules parameter" { - $RequiredModuleName = 'PackageManagement' - $OldrequiredModuleVersion = '1.0.0.0' - $OldRequiredModules = @(@{ModuleName = $RequiredModuleName; ModuleVersion = $OldrequiredModuleVersion }) - $NewrequiredModuleVersion = '2.0.0.0' - $NewRequiredModules = @(@{ModuleName = $RequiredModuleName; ModuleVersion = $NewrequiredModuleVersion }) - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredModules $OldRequiredModules -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredModules $NewRequiredModules - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($RequiredModuleName) | Should -BeTrue - $results.Contains($NewrequiredModuleVersion) | Should -BeTrue - $results -like ".REQUIREDMODULES*$RequiredModuleName*$NewrequiredModuleVersion" | Should -BeTrue - } - - It "Update .ps1 given ReleaseNotes parameter" { - $Description = "Test Description" - $OldReleaseNotes = "Old release notes for script." - $NewReleaseNotes = "New release notes for script." - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ReleaseNotes $OldReleaseNotes -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ReleaseNotes $NewReleaseNotes - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewReleaseNotes) | Should -BeTrue - $results -like ".RELEASENOTES*$NewReleaseNotes" | Should -BeTrue - } - - It "Update .ps1 given Tags parameter" { - $Description = "Test Description" - $OldTag1 = "Tag1" - $OldTag2 = "Tag2" - $NewTag1 = "NewTag1" - $NewTag2 = "NewTag2" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Tags $OldTag1, $OldTag2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -Tags $NewTag1, $NewTag2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewTag1) | Should -BeTrue - $results.Contains($NewTag2) | Should -BeTrue - $results.Contains(".TAGS $NewTag1 $NewTag2") | Should -BeTrue - } - - It "Update .ps1 given ProjectUri parameter" { - $Description = "Test Description" - $OldProjectUri = "https://www.oldtestprojecturi.com/" - $NewProjectUri = "https://www.newtestprojecturi.com/" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ProjectUri $OldProjectUri -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ProjectUri $NewProjectUri - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewProjectUri) | Should -BeTrue - $results.Contains(".PROJECTURI $NewProjectUri") | Should -BeTrue - } - - It "Update .ps1 given LicenseUri parameter" { - $Description = "Test Description" - $OldLicenseUri = "https://www.oldtestlicenseuri.com/" - $NewLicenseUri = "https://www.newtestlicenseuri.com/" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -LicenseUri $OldLicenseUri -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -LicenseUri $NewLicenseUri - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewLicenseUri) | Should -BeTrue - $results.Contains(".LICENSEURI $NewLicenseUri") | Should -BeTrue - } - - It "Update .ps1 given IconUri parameter" { - $Description = "Test Description" - $OldIconUri = "https://www.oldtesticonuri.com/" - $NewIconUri = "https://www.newtesticonuri.com/" - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -IconUri $OldIconUri -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -IconUri $NewIconUri - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewIconUri) | Should -BeTrue - $results.Contains(".ICONURI $NewIconUri") | Should -BeTrue - } - - It "Update .ps1 given ExternalModuleDependencies parameter" { - $Description = "Test Description" - $OldExternalModuleDep1 = "OldExternalModuleDep1" - $OldExternalModuleDep2 = "OldExternalModuleDep2" - $OldExternalModuleDep1FileName = "OldExternalModuleDep1.psm1" - $OldExternalModuleDep2FileName = "OldExternalModuleDep2.psm1" - $OldExternalModuleDepPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldExternalModuleDep1FileName - $OldExternalModuleDepPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldExternalModuleDep2FileName - $null = New-Item -Path $OldExternalModuleDepPath1 -ItemType File -Force - $null = New-Item -Path $OldExternalModuleDepPath2 -ItemType File -Force - - $NewExternalModuleDep1 = "NewExternalModuleDep1" - $NewExternalModuleDep2 = "NewExternalModuleDep2" - $NewExternalModuleDep1FileName = "NewExternalModuleDep1.psm1" - $NewExternalModuleDep2FileName = "NewExternalModuleDep2.psm1" - $NewExternalModuleDepPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewExternalModuleDep1FileName - $NewExternalModuleDepPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewExternalModuleDep2FileName - $null = New-Item -Path $NewExternalModuleDepPath1 -ItemType File -Force - $null = New-Item -Path $NewExternalModuleDepPath2 -ItemType File -Force - - # NOTE: you may need to add the -NestedModules parameter here as well - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ExternalModuleDependencies $OldExternalModuleDep1, $OldExternalModuleDep2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ExternalModuleDependencies $NewExternalModuleDep1, $NewExternalModuleDep2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewExternalModuleDep1) | Should -BeTrue - $results.Contains($NewExternalModuleDep2) | Should -BeTrue - $results -like ".EXTERNALMODULEDEPENDENCIES*$NewExternalModuleDep1*$NewExternalModuleDep2" | Should -BeTrue - } - - It "Update .ps1 given RequiredAssemblies parameter" { - $Description = "Test Description" - $OldRequiredAssembly1 = "OldRequiredAssembly1.dll" - $OldRequiredAssembly2 = "OldRequiredAssembly2.dll" - $OldRequiredAssemblyPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldRequiredAssembly1 - $OldRequiredAssemblyPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldRequiredAssembly2 - $null = New-Item -Path $OldRequiredAssemblyPath1 -ItemType File -Force - $null = New-Item -Path $OldRequiredAssemblyPath2 -ItemType File -Force - - $NewRequiredAssembly1 = "NewRequiredAssembly1.dll" - $NewRequiredAssembly2 = "NewRequiredAssembly2.dll" - $NewRequiredAssemblyPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewRequiredAssembly1 - $NewRequiredAssemblyPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewRequiredAssembly2 - $null = New-Item -Path $NewRequiredAssemblyPath1 -ItemType File -Force - $null = New-Item -Path $NewRequiredAssemblyPath2 -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredAssemblies $OldRequiredAssembly1, $OldRequiredAssembly2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredAssemblies $NewRequiredAssembly1, $NewRequiredAssembly2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewRequiredAssembly1) | Should -BeTrue - $results.Contains($NewRequiredAssembly2) | Should -BeTrue - $results -like ".REQUIREDASSEMBLIES*$NewRequiredAssembly1*$NewRequiredAssembly2" | Should -BeTrue - } - - It "Update .ps1 given NestedModules parameter" { - $Description = "Test Description" - $OldNestedModule1 = "OldNestedModule1" - $OldNestedModule2 = "OldNestedModule2" - $OldNestModuleFileName1 = "OldNestedModule1.dll" - $OldNestModuleFileName2 = "OldNestedModule2.dll" - $OldNestedModulePath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldNestModuleFileName1 - $OldNestedModulePath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldNestModuleFileName2 - $null = New-Item -Path $OldNestedModulePath1 -ItemType File -Force - $null = New-Item -Path $OldNestedModulePath2 -ItemType File -Force - - $NewNestedModule1 = "NewNestedModule1" - $NewNestedModule2 = "NewNestedModule2" - $NewNestModuleFileName1 = "NewNestedModule1.dll" - $NewNestModuleFileName2 = "NewNestedModule2.dll" - $NewNestedModulePath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewNestModuleFileName1 - $NewNestedModulePath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewNestModuleFileName2 - $null = New-Item -Path $NewNestedModulePath1 -ItemType File -Force - $null = New-Item -Path $NewNestedModulePath2 -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -NestedModules $OldNestedModule1, $OldNestedModule2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -NestedModules $NewNestedModule1, $NewNestedModule2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewNestedModule1) | Should -BeTrue - $results.Contains($NewNestedModule2) | Should -BeTrue - $results -like ".NESTEDMODULES*$NewNestedModule1*$NewNestedModule2" | Should -BeTrue - } - - It "Update .ps1 given RequiredScripts parameter" { - $Description = "Test Description" - $OldRequiredScript1 = "OldNestedModule1.ps1" - $OldRequiredScript2 = "OldNestedModule2.ps1" - $OldRequiredScript1Path = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldRequiredScript1 - $OldRequiredScript2Path = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldRequiredScript2 - $null = New-Item -Path $OldRequiredScript1Path -ItemType File -Force - $null = New-Item -Path $OldRequiredScript2Path -ItemType File -Force - - $NewRequiredScript1 = "NewNestedModule1.ps1" - $NewRequiredScript2 = "NewNestedModule2.ps1" - $NewRequiredScript1Path = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewRequiredScript1 - $NewRequiredScript2Path = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewRequiredScript2 - $null = New-Item -Path $NewRequiredScript1Path -ItemType File -Force - $null = New-Item -Path $NewRequiredScript2Path -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredScripts $OldRequiredScript1, $OldRequiredScript2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -RequiredScripts $NewRequiredScript1, $NewRequiredScript2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewRequiredScript1) | Should -BeTrue - $results.Contains($NewRequiredScript2) | Should -BeTrue - $results -like ".REQUIREDSCRIPTS*$NewRequiredScript1*$NewRequiredScript2" | Should -BeTrue - } - - It "Update .ps1 given ExternalScriptDependencies parameter" { - $Description = "Test Description" - $OldExternalScriptDep1 = "OldExternalScriptDep1.ps1" - $OldExternalScriptDep2 = "OldExternalScriptDep2.ps1" - $OldExternalScriptDepPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldExternalScriptDep1 - $OldExternalScriptDepPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $OldExternalScriptDep2 - $null = New-Item -Path $OldExternalScriptDepPath1 -ItemType File -Force - $null = New-Item -Path $OldExternalScriptDepPath2 -ItemType File -Force - - $NewExternalScriptDep1 = "NewExternalScriptDep1.ps1" - $NewExternalScriptDep2 = "NewExternalScriptDep2.ps1" - $NewExternalScriptDepPath1 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewExternalScriptDep1 - $NewExternalScriptDepPath2 = Microsoft.PowerShell.Management\Join-Path -Path $script:TempScriptPath -ChildPath $NewExternalScriptDep2 - $null = New-Item -Path $NewExternalScriptDepPath1 -ItemType File -Force - $null = New-Item -Path $NewExternalScriptDepPath2 -ItemType File -Force - - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ExternalModuleDependencies $OldExternalModuleDep1, $OldExternalModuleDep2 -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -ExternalModuleDependencies $NewExternalModuleDep1, $NewExternalModuleDep2 - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewExternalModuleDep1) | Should -BeTrue - $results.Contains($NewExternalModuleDep2) | Should -BeTrue - $results -like ".EXTERNALSCRIPTDEPENDENCIES*$NewExternalModuleDep1*$NewExternalModuleDep2" | Should -BeTrue - } - - It "Update .ps1 given PrivateData parameter" { - $Description = "Test Description" - $OldPrivateData = @{"OldPrivateDataEntry1" = "OldPrivateDataValue1"} - $NewPrivateData = @{"NewPrivateDataEntry1" = "NewPrivateDataValue1"} - New-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -PrivateData $OldPrivateData -Description $Description - - Update-PSScriptFileInfo -FilePath $script:testPSScriptInfoPath -PrivateData $NewPrivateData - - Test-Path -FilePath $script:testPSScriptInfoPath | Should -BeTrue - $results = Get-Content -Path $script:testManifestPath -Raw - $results.Contains($NewPrivateData) | Should -BeTrue - $results -like ".PRIVATEDATA*$NewPrivateData" | Should -BeTrue - } - - It "Update signed script when using RemoveSignature parameter" { - $scriptName = "ScriptWithSignature.ps1" - $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName - - # use a copy of the signed script file so we can re-use for other tests - $null = Copy-Item -Path $scriptFilePath -Destination $TestDrive - $tmpScriptFilePath = Join-Path -Path $TestDrive -ChildPath $scriptName - - Update-PSScriptFileInfo -FilePath $tmpScriptFilePath -Version "2.0.0.0" -RemoveSignature - Test-PSScriptFileInfo -FilePath $tmpScriptFilePath | Should -Be $true - } - - It "Throw error when attempting to update a signed script without -RemoveSignature parameter" { - $scriptName = "ScriptWithSignature.ps1" - $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName - - # use a copy of the signed script file so we can re-use for other tests - $null = Copy-Item -Path $scriptFilePath -Destination $TestDrive - $tmpScriptFilePath = Join-Path -Path $TestDrive -ChildPath $scriptName - - { Update-PSScriptFileInfo -FilePath $tmpScriptFilePath -Version "2.0.0.0" } | Should -Throw -ErrorId "ScriptToBeUpdatedContainsSignature,Microsoft.PowerShell.PowerShellGet.Cmdlets.UpdatePSScriptFileInfo" - } -#> -} \ No newline at end of file diff --git a/test/testFiles/testScripts/ScriptWithSignature.ps1 b/test/testFiles/testScripts/ScriptWithSignature.ps1 new file mode 100644 index 000000000..d83f3a998 --- /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