From ab14766ddc9d29a4b762c61c731b82ccc77a7fc5 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 19 Jan 2022 21:18:56 -0500 Subject: [PATCH 01/86] fix company name for modules --- src/code/InstallHelper.cs | 12 ++++++++++-- src/code/PSResourceInfo.cs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index e7d3dea58..40601130e 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -320,6 +320,7 @@ private List InstallPackage( { totalInstalledPkgCount++; var tempInstallPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string companyName = String.Empty; try { // Create a temp directory to install to @@ -513,6 +514,12 @@ private List InstallPackage( continue; } + companyName = parsedMetadataHashtable["CompanyName"] as string; + if (!String.IsNullOrEmpty(companyName)) + { + pkg.CompanyName = companyName; + } + moduleManifestVersion = parsedMetadataHashtable["ModuleVersion"] as string; // Accept License verification @@ -533,7 +540,7 @@ private List InstallPackage( if (_includeXML) { - CreateMetadataXMLFile(tempDirNameVersion, installPath, pkg, isModule); + CreateMetadataXMLFile(tempDirNameVersion, installPath, companyName, pkg, isModule); } MoveFilesIntoInstallPath( @@ -714,7 +721,7 @@ private bool DetectClobber(string pkgName, Hashtable parsedMetadataHashtable) return foundClobber; } - private void CreateMetadataXMLFile(string dirNameVersion, string installPath, PSResourceInfo pkg, bool isModule) + private void CreateMetadataXMLFile(string dirNameVersion, string installPath, string companyName, PSResourceInfo pkg, bool isModule) { // Script will have a metadata file similar to: "TestScript_InstalledScriptInfo.xml" // Modules will have the metadata file: "PSGetModuleInfo.xml" @@ -723,6 +730,7 @@ private void CreateMetadataXMLFile(string dirNameVersion, string installPath, PS pkg.InstalledDate = DateTime.Now; pkg.InstalledLocation = installPath; + pkg.CompanyName = companyName; // Write all metadata into metadataXMLPath if (!pkg.TryWrite(metadataXMLPath, out string error)) diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 5b434c055..5a4ce3eba 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -234,7 +234,7 @@ public sealed class PSResourceInfo public Dictionary AdditionalMetadata { get; } public string Author { get; } - public string CompanyName { get; } + public string CompanyName { get; internal set; } public string Copyright { get; } public Dependency[] Dependencies { get; } public string Description { get; } From 0d62ecbdd5d2887b4a2cb12986a8cfad60f37b1e Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 28 Jan 2022 14:56:32 -0500 Subject: [PATCH 02/86] parse psScriptInfo comment out to hashtable --- src/code/InstallHelper.cs | 10 ++- src/code/Utils.cs | 176 +++++++++++++++++++++++++------------- 2 files changed, 125 insertions(+), 61 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index e7d3dea58..c77f6fff1 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -527,7 +527,15 @@ private List InstallPackage( continue; } } - + else + { + if (!Utils.TryParseScriptFileInfo(scriptPath, _cmdletPassedIn, out Hashtable parsedPSScriptInfoHashtable)) + { + // Ran into errors parsing the module manifest file which was found in Utils.ParseModuleManifest() and written. + continue; + + } + } // Delete the extra nupkg related files that are not needed and not part of the module/script DeleteExtraneousFiles(pkgIdentity, tempDirNameVersion); diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 54fd85e72..b7f3e2cff 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -25,47 +26,6 @@ internal static class Utils public static readonly string[] EmptyStrArray = Array.Empty(); - private const string ConvertJsonToHashtableScript = @" - param ( - [string] $json - ) - - function ConvertToHash - { - param ( - [pscustomobject] $object - ) - - $output = @{} - $object | Microsoft.PowerShell.Utility\Get-Member -MemberType NoteProperty | ForEach-Object { - $name = $_.Name - $value = $object.($name) - - if ($value -is [object[]]) - { - $array = @() - $value | ForEach-Object { - $array += (ConvertToHash $_) - } - $output.($name) = $array - } - elseif ($value -is [pscustomobject]) - { - $output.($name) = (ConvertToHash $value) - } - else - { - $output.($name) = $value - } - } - - $output - } - - $customObject = Microsoft.PowerShell.Utility\ConvertFrom-Json -InputObject $json - return ConvertToHash $customObject - "; - #endregion #region String methods @@ -769,6 +729,121 @@ public static bool TryParseModuleManifest( var psdataParseError = new ErrorRecord(ex, "psdataParseError", ErrorCategory.ParserError, null); cmdletPassedIn.WriteError(psdataParseError); } + } + } + + return successfullyParsed; + } + + public static bool TryParseScriptFileInfo( + string scriptFileInfo, + PSCmdlet cmdletPassedIn, + out Hashtable parsedPSScriptInfoHashtable) + { + parsedPSScriptInfoHashtable = new Hashtable(); + bool successfullyParsed = false; + + if (scriptFileInfo.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + { + // Parse the script file + var ast = Parser.ParseFile( + scriptFileInfo, + out Token[] tokens, + out ParseError[] errors); + + if (errors.Length > 0) + { + var message = String.Format("Could not parse '{0}' as a PowerShell script file.", scriptFileInfo); + var ex = new ArgumentException(message); + var psScriptFileParseError = new ErrorRecord(ex, "psScriptFileParseError", ErrorCategory.ParserError, null); + cmdletPassedIn.WriteError(psScriptFileParseError); + return successfullyParsed; + } + else if (ast != null) + { + // TODO: Anam do we still need to check if ast is not null? + // Get the block/group comment beginning with <#PSScriptInfo + List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); + string commentPattern = "<#PSScriptInfo"; + Regex rg = new Regex(commentPattern); + List psScriptInfoCommentTokens = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).ToList(); + + if (psScriptInfoCommentTokens.Count() == 0 || psScriptInfoCommentTokens[0] == null) + { + // TODO: Anam change to error from V2 + var message = String.Format("PSScriptInfo comment was missing or could not be parsed"); + var ex = new ArgumentException(message); + var psCommentParseError = new ErrorRecord(ex, "psScriptInfoCommentParseError", ErrorCategory.ParserError, null); + cmdletPassedIn.WriteError(psCommentParseError); + return successfullyParsed; + } + + string[] commentLines = Regex.Split(psScriptInfoCommentTokens[0].Text, "[\r\n]"); + // TODO: Anam clean above up as we don't need to store empty lines for CF and newline, I think? + string keyName = String.Empty; + string value = 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 + #> + */ + + /** + If comment line count is not more than two, it doesn't have the any metadata property + comment block would look like: + + <#PSScriptInfo + #> + */ + cmdletPassedIn.WriteVerbose("total comment lines: " + commentLines.Count()); + if (commentLines.Count() > 2) + { + for (int i = 1; i < commentLines.Count(); i++) + { + string line = commentLines[i]; + cmdletPassedIn.WriteVerbose("i: " + i + "line: " + line); + if (String.IsNullOrEmpty(line)) + { + continue; + } + // A line is starting with . conveys a new metadata property + // __NEWLINE__ is used for replacing the value lines while adding the value to $PSScriptInfo object + if (line.Trim().StartsWith(".")) + { + // string partPattern = "[.\s+]"; + string[] parts = line.Trim().TrimStart('.').Split(); + keyName = parts[0]; + value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; + parsedPSScriptInfoHashtable.Add(keyName, value); + } + } + } + + + + + + } } @@ -795,25 +870,6 @@ public static void WriteVerboseOnCmdlet( catch { } } - /// - /// Convert a json string into a hashtable object. - /// This uses custom script to perform the PSObject -> Hashtable - /// conversion, so that this works with WindowsPowerShell. - /// - public static Hashtable ConvertJsonToHashtable( - PSCmdlet cmdlet, - string json) - { - Collection results = cmdlet.InvokeCommand.InvokeScript( - script: ConvertJsonToHashtableScript, - useNewScope: true, - writeToPipeline: PipelineResultTypes.Error, - input: null, - args: new object[] { json }); - - return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; - } - #endregion #region Directory and File From bd47e2b3d708a415fb247eebe3b3165c308000ff Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 31 Jan 2022 11:59:33 -0500 Subject: [PATCH 03/86] merge with changes from master for Utils.cs --- src/code/Utils.cs | 62 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index b7f3e2cff..b8e6be773 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -1,4 +1,3 @@ -using System.Text.RegularExpressions; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -15,6 +14,7 @@ using System.Management.Automation.Runspaces; using System.Runtime.InteropServices; using System.Security; +using System.Text.RegularExpressions; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -26,6 +26,47 @@ internal static class Utils public static readonly string[] EmptyStrArray = Array.Empty(); + private const string ConvertJsonToHashtableScript = @" + param ( + [string] $json + ) + + function ConvertToHash + { + param ( + [pscustomobject] $object + ) + + $output = @{} + $object | Microsoft.PowerShell.Utility\Get-Member -MemberType NoteProperty | ForEach-Object { + $name = $_.Name + $value = $object.($name) + + if ($value -is [object[]]) + { + $array = @() + $value | ForEach-Object { + $array += (ConvertToHash $_) + } + $output.($name) = $array + } + elseif ($value -is [pscustomobject]) + { + $output.($name) = (ConvertToHash $value) + } + else + { + $output.($name) = $value + } + } + + $output + } + + $customObject = Microsoft.PowerShell.Utility\ConvertFrom-Json -InputObject $json + return ConvertToHash $customObject + "; + #endregion #region String methods @@ -870,6 +911,25 @@ public static void WriteVerboseOnCmdlet( catch { } } + /// + /// Convert a json string into a hashtable object. + /// This uses custom script to perform the PSObject -> Hashtable + /// conversion, so that this works with WindowsPowerShell. + /// + public static Hashtable ConvertJsonToHashtable( + PSCmdlet cmdlet, + string json) + { + Collection results = cmdlet.InvokeCommand.InvokeScript( + script: ConvertJsonToHashtableScript, + useNewScope: true, + writeToPipeline: PipelineResultTypes.Error, + input: null, + args: new object[] { json }); + + return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; + } + #endregion #region Directory and File From 7bd8c101d0f633b93db407616b38f1065e7ca7dd Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 31 Jan 2022 12:00:36 -0500 Subject: [PATCH 04/86] remove extra lines --- src/code/Utils.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index b8e6be773..d2e9ff0dd 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -879,12 +879,6 @@ Feature 5 } } } - - - - - - } } From 5db7d3b23b6f5d915d6618ecf83b0ec7f607ea5a Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 31 Jan 2022 13:04:23 -0500 Subject: [PATCH 05/86] set successfullyParsed variable --- src/code/Utils.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index d2e9ff0dd..c153c3d9f 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -859,6 +860,7 @@ Feature 5 cmdletPassedIn.WriteVerbose("total comment lines: " + commentLines.Count()); if (commentLines.Count() > 2) { + // TODO: Anam is it an error if the metadata property is empty? for (int i = 1; i < commentLines.Count(); i++) { string line = commentLines[i]; @@ -878,7 +880,15 @@ Feature 5 parsedPSScriptInfoHashtable.Add(keyName, value); } } + successfullyParsed = true; } + + // CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); + // if (scriptCommentInfo != null && !String.IsNullOrEmpty(scriptCommentInfo.Description)) + // { + + // } + } } From 77e29c69c6718a6c7916bce11e626ef290b85043 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 31 Jan 2022 16:53:50 -0500 Subject: [PATCH 06/86] change TestRequiredResourceFile.json path referenced to not be relative --- src/code/Utils.cs | 4 ++-- test/InstallPSResource.Tests.ps1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index c153c3d9f..5fe4b05ad 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -793,13 +793,13 @@ public static bool TryParseScriptFileInfo( out Token[] tokens, out ParseError[] errors); - if (errors.Length > 0) + if (errors.Length > 0 && !String.Equals(errors[0].ErrorId, "WorkflowNotSupportedInPowerShellCore", StringComparison.OrdinalIgnoreCase)) { var message = String.Format("Could not parse '{0}' as a PowerShell script file.", scriptFileInfo); var ex = new ArgumentException(message); var psScriptFileParseError = new ErrorRecord(ex, "psScriptFileParseError", ErrorCategory.ParserError, null); cmdletPassedIn.WriteError(psScriptFileParseError); - return successfullyParsed; + return successfullyParsed; } else if (ast != null) { diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index bf73e0fb2..d23275536 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -382,7 +382,7 @@ Describe 'Test Install-PSResource for Module' { } It "Install modules using -RequiredResourceFile with JSON file" { - $rrFileJSON = ".\$RequiredResourceJSONFileName" + $rrFileJSON = Join-Path -Path $psscriptroot -ChildPath "test" -AdditionalChildPath $RequiredResourceJSONFileName Install-PSResource -RequiredResourceFile $rrFileJSON -Verbose From b74077be9ae8e24690d4be91bc7185b4d9f713f5 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 4 Feb 2022 10:37:29 -0500 Subject: [PATCH 07/86] skeleton for *-FileScriptInfo cmdlets --- help/New-ScriptFileInfo.md | 320 ++++++++++++++++++++++++++++++++++ src/code/NewScriptFileInfo.cs | 187 ++++++++++++++++++++ src/code/PSScriptFileInfo.cs | 280 +++++++++++++++++++++++++++++ src/code/Utils.cs | 42 ++++- 4 files changed, 826 insertions(+), 3 deletions(-) create mode 100644 help/New-ScriptFileInfo.md create mode 100644 src/code/NewScriptFileInfo.cs create mode 100644 src/code/PSScriptFileInfo.cs diff --git a/help/New-ScriptFileInfo.md b/help/New-ScriptFileInfo.md new file mode 100644 index 000000000..5ccdae874 --- /dev/null +++ b/help/New-ScriptFileInfo.md @@ -0,0 +1,320 @@ +--- +external help file: PowerShellGet.dll-Help.xml +Module Name: PowerShellGet +online version: +schema: 2.0.0 +--- + +# New-ScriptFileInfo + +## SYNOPSIS +Returns resources (modules and scripts) installed on the machine via PowerShellGet. + +## SYNTAX + +``` +New-ScriptFileInfo [-Path ] [] +``` + +## DESCRIPTION +The Get-PSResource cmdlet combines the Get-InstalledModule, Get-InstalledScript cmdlets from V2. It performs a search within module or script installation paths based on the -Name parameter argument. It returns PSResourceInfo objects which describes each resource item found. Other parameters allow the returned results to be filtered by version and path. + +## EXAMPLES + + +## PARAMETERS + +### -Path +The path the .ps1 script info file will be created at. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: ?? +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Version +The version of the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: ?? +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Author +The author of the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: ?? +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Description +The description of the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: ?? +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -GUID +The GUID for the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: ?? +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -CompanyName +The name of the company owning the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: ?? +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Copyright +The copyright information for the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -RequiredModules +The list of modules required for the script. + +```yaml +Type: System.String[] # this was Object[] in V2 TODO, determine type +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExternalModuleDependencies +The list of external module dependencies taken by this script + +```yaml +Type: System.String[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -RequiredScripts +The list of scripts required by the script + +```yaml +Type: System.String[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExternalScriptDependencies +The list of external script dependencies taken by this script + +```yaml +Type: System.String[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Tags +The tags associated with the script. + +```yaml +Type: System.String[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProjectUri +The Uri for the project associated with the script + +```yaml +Type: System.Uri +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LicenseUri +The Uri for the license associated with the script + +```yaml +Type: System.Uri +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -IconUri +The Uri for the icon associated with the script + +```yaml +Type: System.Uri +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ReleaseNotes +The release notes for the script + +```yaml +Type: System.String[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PrivateData +The private data associated with the script + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). + +## OUTPUTS + +### Microsoft.PowerShell.PowerShellGet.UtilClasses.PSResourceInfo +``` +PSResourceInfo : { + AdditionalMetadata + Author + CompanyName + Copyright + Dependencies + Description + IconUri + Includes + InstalledDate + InstalledLocation + IsPrerelease + LicenseUri + Name + PackageManagementProvider + PowerShellGetFormatVersion + Prerelease + ProjectUri + PublishedDate + ReleaseNotes + Repository + RepositorySourceLocation + Tags + Type + UpdatedDate + Version +} +``` + +## NOTES + +## RELATED LINKS diff --git a/src/code/NewScriptFileInfo.cs b/src/code/NewScriptFileInfo.cs new file mode 100644 index 000000000..4959971c6 --- /dev/null +++ b/src/code/NewScriptFileInfo.cs @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using NuGet.Versioning; +using System; +using System.Collections.Generic; +using System.Management.Automation; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// It retrieves a resource that was installed with Install-PSResource + /// Returns a single resource or multiple resource. + /// + [Cmdlet(VerbsCommon.New, "ScriptFileInfo")] + public sealed class NewScriptFileInfo : PSCmdlet + { + #region Members + + private VersionRange _versionRange; + private List _pathsToSearch; + + #endregion + + #region Parameters + + /// + /// The path the .ps1 script info file will be created at + /// + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty] + public string Path { 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; } + + /// + /// The GUID for the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public Guid Guid { get; set; } + + /// + /// The name of the company owning the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string CompanyName { get; set; } + + /// + /// The copyright information for the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Copyright { get; set; } + + /// + /// The list of modules required by the script + /// TODO: in V2 this had type Object[] + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] 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 Uri ProjectUri { get; set; } + + /// + /// The Uri for the license associated with the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public Uri LicenseUri { get; set; } + + /// + /// The Uri for the icon associated with the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public Uri 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 specified, passes the contents of the created .ps1 file to the console + /// If -Path is not specified, then .ps1 contents will just be written out for the user + /// + [Parameter] + public SwitchParameter PassThru { get; set; } + + #endregion + + #region Methods + + protected override void BeginProcessing() + { + // do Uri's come in as string or Uri? + // validate if it comes in as string + + // validate string + } + + protected override void ProcessRecord() + { + // if not Path, is PassThru provided? + // what does PassThru do in lieu of Path? + + // for all non mandatory params, set default value + + // get PSScriptInfo string --> take PSScriptInfo comment keys (from params passed in) and validate and turn into string + // get Requires string --> from params passed in + // get CommentHelpInfo string --> from params passed in + + // commpose totalMetadata string, which contains PSScriptInfo string + Requires string + CommentHelpInfo string + + // write to file at path. Call Test-ScriptFileInfo. + // If PassThru write content of file + + } + + #endregion + } +} diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs new file mode 100644 index 000000000..5810a025b --- /dev/null +++ b/src/code/PSScriptFileInfo.cs @@ -0,0 +1,280 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Management.Automation; + +namespace Microsoft.PowerShell.PowerShellGet.UtilClasses +{ + /// + /// This class contains information for a repository item. + /// + public sealed class PSScriptFileInfo + { + #region Constructor + + public PSScriptFileInfo(string name, Uri url, int priority, bool trusted, PSCredentialInfo credentialInfo) + { + Name = name; + Url = url; + Priority = priority; + Trusted = trusted; + CredentialInfo = credentialInfo; + } + + /*** + + <#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 + #> + */ + + #endregion + + #region Properties + + /// + /// the Version of the script + /// + public string Version { get; } + + /// + /// the GUID for the script + /// + public string Guid { get; } + + /// + /// the author for the script + /// + public string Author { get; } + + /// + /// the name of the company owning the script + /// + [ValidateRange(0, 50)] + public string CompanyName { get; } + + /// + /// the copyright information for the script + /// + public string Copyright { get; } + + /// + /// the tags for the script + /// + public string[] Tags { get; } + + /// + /// the Uri for the license of the script + /// + public Uri LicenseUri { get; } + + /// + /// the Uri for the project relating to the script + /// + public Uri ProjectUri { get; } + + /// + /// the Uri for the icon relating to the script + /// + public Uri IconUri { get; } + + /// + /// the list of external module dependencies for the script + /// + public string[] ExternalModuleDependencies { get; } + + /// + /// the list of required scripts for the parent script + /// + public string[] RequiredScripts { get; } + + /// + /// the list of external script dependencies for the script + /// + public string[] ExternalScriptDependencies { get; } + + /// + /// the release notes relating to the script + /// + public string ReleaseNotes { get; } + + #endregion + } + + #region Public Static Methods + + public static bool TryParseScriptFileInfo( + string scriptFileInfo, + PSCmdlet cmdletPassedIn, + out Hashtable parsedPSScriptInfoHashtable) + { + parsedPSScriptInfoHashtable = new Hashtable(); + bool successfullyParsed = false; + + if (scriptFileInfo.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + { + // Parse the script file + var ast = Parser.ParseFile( + scriptFileInfo, + out Token[] tokens, + out ParseError[] errors); + + if (errors.Length > 0 && !String.Equals(errors[0].ErrorId, "WorkflowNotSupportedInPowerShellCore", StringComparison.OrdinalIgnoreCase)) + { + var message = String.Format("Could not parse '{0}' as a PowerShell script file.", scriptFileInfo); + var ex = new ArgumentException(message); + var psScriptFileParseError = new ErrorRecord(ex, "psScriptFileParseError", ErrorCategory.ParserError, null); + cmdletPassedIn.WriteError(psScriptFileParseError); + return successfullyParsed; + } + else if (ast != null) + { + // TODO: Anam do we still need to check if ast is not null? + // Get the block/group comment beginning with <#PSScriptInfo + List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); + string commentPattern = "<#PSScriptInfo"; + Regex rg = new Regex(commentPattern); + List psScriptInfoCommentTokens = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).ToList(); + + if (psScriptInfoCommentTokens.Count() == 0 || psScriptInfoCommentTokens[0] == null) + { + // TODO: Anam change to error from V2 + var message = String.Format("PSScriptInfo comment was missing or could not be parsed"); + var ex = new ArgumentException(message); + var psCommentParseError = new ErrorRecord(ex, "psScriptInfoCommentParseError", ErrorCategory.ParserError, null); + cmdletPassedIn.WriteError(psCommentParseError); + return successfullyParsed; + } + + string[] commentLines = Regex.Split(psScriptInfoCommentTokens[0].Text, "[\r\n]"); + // TODO: Anam clean above up as we don't need to store empty lines for CF and newline, I think? + string keyName = String.Empty; + string value = 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 + #> + */ + + /** + If comment line count is not more than two, it doesn't have the any metadata property + comment block would look like: + + <#PSScriptInfo + #> + */ + cmdletPassedIn.WriteVerbose("total comment lines: " + commentLines.Count()); + if (commentLines.Count() > 2) + { + // TODO: Anam is it an error if the metadata property is empty? + for (int i = 1; i < commentLines.Count(); i++) + { + string line = commentLines[i]; + cmdletPassedIn.WriteVerbose("i: " + i + "line: " + line); + if (String.IsNullOrEmpty(line)) + { + continue; + } + // A line is starting with . conveys a new metadata property + // __NEWLINE__ is used for replacing the value lines while adding the value to $PSScriptInfo object + if (line.Trim().StartsWith(".")) + { + // string partPattern = "[.\s+]"; + string[] parts = line.Trim().TrimStart('.').Split(); + keyName = parts[0]; + value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; + parsedPSScriptInfoHashtable.Add(keyName, value); + } + } + successfullyParsed = true; + } + + // get .DESCRIPTION comment + CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); + if (scriptCommentInfo != null && !String.IsNullOrEmpty(scriptCommentInfo.Description)) + { + parsedPSScriptInfoHashtable.Add("DESCRIPTION", scriptCommentInfo.Description); + } + + // get RequiredModules + ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; + if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) + { + ReadOnlyCollection parsedRequiredModules = parsedScriptRequirements.RequiredModules; + parsedPSScriptInfoHashtable.Add("RequiredModules", parsedRequiredModules); + } + + // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow + // TODO: Anam DefinedWorkflow is no longer supported as of PowerShellCore 6+, do we ignore error or reject script? + // List parsedFunctionAst = ast.FindAll(a => a.GetType().Name == "FunctionDefinitionAst", true).ToList(); + // foreach (var p in parsedFunctionAst) + // { + // Console.WriteLine(p.Parent.Extent.Text); + // p. + // } + var parsedFunctionAst = ast.FindAll(a => a is FunctionDefinitionAst, true); + List allCommands = new List(); + if (allCommands.Count() > 0) + { + foreach (var function in parsedFunctionAst) + { + allCommands.Add((FunctionDefinitionAst) function); + } + + List allCommandNames = allCommands.Select(a => a.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedCommands", allCommandNames); + + // b.Body.Extent.Text to get actual content. So "Function b.Name c.Body.Extent.Text" is that whole line lol + List allFunctionNames = allCommands.Where(a => !a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedFunctions", allFunctionNames); + + List allWorkflowNames = allCommands.Where(a => a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); + } + } + } + + return successfullyParsed; + } + + #endregion +} diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 5fe4b05ad..c07c21e06 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -883,12 +883,48 @@ Feature 5 successfullyParsed = true; } - // CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); - // if (scriptCommentInfo != null && !String.IsNullOrEmpty(scriptCommentInfo.Description)) - // { + // get .DESCRIPTION comment + CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); + if (scriptCommentInfo != null && !String.IsNullOrEmpty(scriptCommentInfo.Description)) + { + parsedPSScriptInfoHashtable.Add("DESCRIPTION", scriptCommentInfo.Description); + } + // get RequiredModules + ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; + if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) + { + ReadOnlyCollection parsedRequiredModules = parsedScriptRequirements.RequiredModules; + parsedPSScriptInfoHashtable.Add("RequiredModules", parsedRequiredModules); + } + + // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow + // TODO: Anam DefinedWorkflow is no longer supported as of PowerShellCore 6+, do we ignore error or reject script? + // List parsedFunctionAst = ast.FindAll(a => a.GetType().Name == "FunctionDefinitionAst", true).ToList(); + // foreach (var p in parsedFunctionAst) + // { + // Console.WriteLine(p.Parent.Extent.Text); + // p. // } + var parsedFunctionAst = ast.FindAll(a => a is FunctionDefinitionAst, true); + List allCommands = new List(); + if (allCommands.Count() > 0) + { + foreach (var function in parsedFunctionAst) + { + allCommands.Add((FunctionDefinitionAst) function); + } + List allCommandNames = allCommands.Select(a => a.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedCommands", allCommandNames); + + // b.Body.Extent.Text to get actual content. So "Function b.Name c.Body.Extent.Text" is that whole line lol + List allFunctionNames = allCommands.Where(a => !a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedFunctions", allFunctionNames); + + List allWorkflowNames = allCommands.Where(a => a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); + } } } From 92b0eaad4c61631b6e4a377f13a05f959ef0efdd Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 4 Feb 2022 16:00:54 -0500 Subject: [PATCH 08/86] skeleton for PSSCriptFileInfo.cs --- src/code/NewScriptFileInfo.cs | 33 ++++++++- src/code/PSScriptFileInfo.cs | 129 +++++++++++++++++++++++----------- 2 files changed, 119 insertions(+), 43 deletions(-) diff --git a/src/code/NewScriptFileInfo.cs b/src/code/NewScriptFileInfo.cs index 4959971c6..9afe9c5b7 100644 --- a/src/code/NewScriptFileInfo.cs +++ b/src/code/NewScriptFileInfo.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -5,6 +6,7 @@ using NuGet.Versioning; using System; using System.Collections.Generic; +using System.IO; using System.Management.Automation; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets @@ -152,6 +154,12 @@ public sealed class NewScriptFileInfo : PSCmdlet [Parameter] public SwitchParameter PassThru { 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 @@ -166,8 +174,29 @@ protected override void BeginProcessing() protected override void ProcessRecord() { - // if not Path, is PassThru provided? - // what does PassThru do in lieu of Path? + if (!String.IsNullOrEmpty(Path) && !Path.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); + } + + if (!String.IsNullOrEmpty(Path) && File.Exists(Path) && !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); + } + + // at this point, we've verified Path was passed in and doesn't exist or does and uses Force. + // OR, Path isn't passed in, which is also ok. + + PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( + + ) // for all non mandatory params, set default value diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 5810a025b..94f8fae82 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -1,8 +1,10 @@ +using Microsoft.VisualBasic.CompilerServices; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; using System.Management.Automation; +using System.Runtime.InteropServices; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -11,55 +13,17 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses /// public sealed class PSScriptFileInfo { - #region Constructor - - public PSScriptFileInfo(string name, Uri url, int priority, bool trusted, PSCredentialInfo credentialInfo) - { - Name = name; - Url = url; - Priority = priority; - Trusted = trusted; - CredentialInfo = credentialInfo; - } - - /*** - - <#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 - #> - */ - - #endregion - #region Properties /// /// the Version of the script /// - public string Version { get; } + public Version Version { get; } /// /// the GUID for the script /// - public string Guid { get; } + public Guid Guid { get; } /// /// the author for the script @@ -115,9 +79,92 @@ Feature 5 /// /// the release notes relating to the script /// - public string ReleaseNotes { get; } + public string[] ReleaseNotes { get; } + + /// + /// The private data associated with the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string PrivateData { get; set; } + + /// + /// The description of the script + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string Description { get; set; } + + #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, + string[] externalModuleDependencies, + string[] requiredScripts, + string[] externalScriptDependencies, + string[] releaseNotes, + string privateData, + string description + ) + { + if (String.IsNullOrEmpty(author)) + { + author = Environment.UserName; + } + Version = !String.IsNullOrEmpty(version) ? new Version (version) : new Version("1.0.0.0"); + Guid = (guid == null || guid == Guid.Empty) ? new Guid() : guid; + Author = !String.IsNullOrEmpty(author) ? author : Environment.UserName; + CompanyName = companyName; + Copyright = copyright; + Tags = tags ?? Utils.EmptyStrArray; + LicenseUri = licenseUri; + ProjectUri = projectUri; + IconUri = iconUri; + ExternalModuleDependencies = externalModuleDependencies ?? Utils.EmptyStrArray; + RequiredScripts = requiredScripts ?? Utils.EmptyStrArray; + ExternalScriptDependencies = externalScriptDependencies ?? Utils.EmptyStrArray; + ReleaseNotes = releaseNotes ?? Utils.EmptyStrArray; + PrivateData = privateData; + Description = description; + } + /*** + + <#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 + #> + */ #endregion + } #region Public Static Methods From b6847621f13e4522447d5eb3fdbee1abf6585a55 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Sat, 5 Feb 2022 21:28:59 -0500 Subject: [PATCH 09/86] PSScriptInfo comment of New-PSScriptFileInfo cmdlet works --- src/PowerShellGet.psd1 | 3 +- ...riptFileInfo.cs => NewPSScriptFileInfo.cs} | 84 +++-- src/code/PSScriptFileInfo.cs | 320 ++++++++++-------- 3 files changed, 237 insertions(+), 170 deletions(-) rename src/code/{NewScriptFileInfo.cs => NewPSScriptFileInfo.cs} (70%) diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index 4a48d4cde..ee441d669 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -24,7 +24,8 @@ 'Publish-PSResource', 'Uninstall-PSResource', 'Unregister-PSResourceRepository', - 'Update-PSResource') + 'Update-PSResource', + 'New-PSScriptFileInfo') VariablesToExport = 'PSGetPath' AliasesToExport = @('inmo', 'fimo', 'upmo', 'pumo') diff --git a/src/code/NewScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs similarity index 70% rename from src/code/NewScriptFileInfo.cs rename to src/code/NewPSScriptFileInfo.cs index 9afe9c5b7..24b52226f 100644 --- a/src/code/NewScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -15,13 +15,13 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets /// It retrieves a resource that was installed with Install-PSResource /// Returns a single resource or multiple resource. /// - [Cmdlet(VerbsCommon.New, "ScriptFileInfo")] - public sealed class NewScriptFileInfo : PSCmdlet + [Cmdlet(VerbsCommon.New, "PSScriptFileInfo")] + public sealed class NewPSScriptFileInfo : PSCmdlet { #region Members - - private VersionRange _versionRange; - private List _pathsToSearch; + private Uri _projectUri; + private Uri _licenseUri; + private Uri _iconUri; #endregion @@ -117,21 +117,21 @@ public sealed class NewScriptFileInfo : PSCmdlet /// [Parameter] [ValidateNotNullOrEmpty()] - public Uri ProjectUri { get; set; } + public string ProjectUri { get; set; } /// /// The Uri for the license associated with the script /// [Parameter] [ValidateNotNullOrEmpty()] - public Uri LicenseUri { get; set; } + public string LicenseUri { get; set; } /// /// The Uri for the icon associated with the script /// [Parameter] [ValidateNotNullOrEmpty()] - public Uri IconUri { get; set; } + public string IconUri { get; set; } /// /// The release notes for the script @@ -164,16 +164,39 @@ public sealed class NewScriptFileInfo : PSCmdlet #region Methods - protected override void BeginProcessing() + protected override void ProcessRecord() { - // do Uri's come in as string or Uri? - // validate if it comes in as string + WriteVerbose("In Anam's cmdlet!"); + // validate Uri related parameters passed in as strings + if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUrl(uriString: ProjectUri, + cmdletPassedIn: this, + uriResult: out _projectUri, + errorRecord: out ErrorRecord projectErrorRecord)) + { + ThrowTerminatingError(projectErrorRecord); + } - // validate string - } + if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUrl(uriString: LicenseUri, + cmdletPassedIn: this, + uriResult: out _licenseUri, + errorRecord: out ErrorRecord licenseErrorRecord)) + { + ThrowTerminatingError(licenseErrorRecord); + } - protected override void ProcessRecord() - { + if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUrl(uriString: IconUri, + cmdletPassedIn: this, + uriResult: out _iconUri, + errorRecord: out ErrorRecord iconErrorRecord)) + { + ThrowTerminatingError(iconErrorRecord); + } + + WriteVerbose("past Uri validation"); + + // determine whether script contents will be written out to .ps1 file or to console + // case 1: Path passed in. If path already exists Force required to overwrite. Else write error. + // case 2: no Path passed in. PassThru required to print script file contents to console. if (!String.IsNullOrEmpty(Path) && !Path.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { var exMessage = "Path needs to end with a .ps1 file. Example: C:/Users/john/x/MyScript.ps1"; @@ -189,16 +212,31 @@ protected override void ProcessRecord() var ex = new ArgumentException(exMessage); var ScriptAtPathAlreadyExistsError = new ErrorRecord(ex, "ScriptAtPathAlreadyExists", ErrorCategory.InvalidArgument, null); ThrowTerminatingError(ScriptAtPathAlreadyExistsError); - } - - // at this point, we've verified Path was passed in and doesn't exist or does and uses Force. - // OR, Path isn't passed in, which is also ok. + } - PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( - - ) + WriteVerbose("past path validation stuff"); - // for all non mandatory params, set default value + PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( + version: Version, + guid: Guid, + author: Author, + companyName: CompanyName, + copyright: Copyright, + tags: Tags, + licenseUri: _licenseUri, + projectUri: _projectUri, + iconUri: _iconUri, + externalModuleDependencies: ExternalModuleDependencies, + requiredScripts: RequiredScripts, + externalScriptDependencies: ExternalScriptDependencies, + releaseNotes: ReleaseNotes, + privateData: PrivateData, + description: Description); + + if (currentScriptInfo.TryCreatePSScriptInfoString(out string psScriptInfoString)) + { + WriteVerbose(psScriptInfoString); + } // get PSScriptInfo string --> take PSScriptInfo comment keys (from params passed in) and validate and turn into string // get Requires string --> from params passed in diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 94f8fae82..dafc5eb7b 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -1,10 +1,15 @@ -using Microsoft.VisualBasic.CompilerServices; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; +using System.Collections; +using System.Collections.Generic; using System.Management.Automation; +using System.Management.Automation.Language; using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Linq; +using System.Collections.ObjectModel; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -138,8 +143,61 @@ string description PrivateData = privateData; Description = description; } - /*** + #endregion + + #region Public Static Methods + + public static bool TryParseScriptFileInfo( + string scriptFileInfo, + PSCmdlet cmdletPassedIn, + out Hashtable parsedPSScriptInfoHashtable) + { + parsedPSScriptInfoHashtable = new Hashtable(); + bool successfullyParsed = false; + + if (scriptFileInfo.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + { + // Parse the script file + var ast = Parser.ParseFile( + scriptFileInfo, + out Token[] tokens, + out ParseError[] errors); + + if (errors.Length > 0 && !String.Equals(errors[0].ErrorId, "WorkflowNotSupportedInPowerShellCore", StringComparison.OrdinalIgnoreCase)) + { + var message = String.Format("Could not parse '{0}' as a PowerShell script file.", scriptFileInfo); + var ex = new ArgumentException(message); + var psScriptFileParseError = new ErrorRecord(ex, "psScriptFileParseError", ErrorCategory.ParserError, null); + cmdletPassedIn.WriteError(psScriptFileParseError); + return successfullyParsed; + } + else if (ast != null) + { + // TODO: Anam do we still need to check if ast is not null? + // Get the block/group comment beginning with <#PSScriptInfo + List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); + string commentPattern = "<#PSScriptInfo"; + Regex rg = new Regex(commentPattern); + List psScriptInfoCommentTokens = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).ToList(); + + if (psScriptInfoCommentTokens.Count() == 0 || psScriptInfoCommentTokens[0] == null) + { + // TODO: Anam change to error from V2 + var message = String.Format("PSScriptInfo comment was missing or could not be parsed"); + var ex = new ArgumentException(message); + var psCommentParseError = new ErrorRecord(ex, "psScriptInfoCommentParseError", ErrorCategory.ParserError, null); + cmdletPassedIn.WriteError(psCommentParseError); + return successfullyParsed; + } + + string[] commentLines = Regex.Split(psScriptInfoCommentTokens[0].Text, "[\r\n]"); + // TODO: Anam clean above up as we don't need to store empty lines for CF and newline, I think? + string keyName = String.Empty; + string value = String.Empty; + + /** + PSScriptInfo comment will be in following format: <#PSScriptInfo .VERSION 1.0 .GUID 544238e3-1751-4065-9227-be105ff11636 @@ -161,167 +219,137 @@ Feature 3 Feature 4 Feature 5 #> - */ + */ - #endregion + /** + If comment line count is not more than two, it doesn't have the any metadata property + comment block would look like: - } - - #region Public Static Methods - - public static bool TryParseScriptFileInfo( - string scriptFileInfo, - PSCmdlet cmdletPassedIn, - out Hashtable parsedPSScriptInfoHashtable) - { - parsedPSScriptInfoHashtable = new Hashtable(); - bool successfullyParsed = false; - - if (scriptFileInfo.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) - { - // Parse the script file - var ast = Parser.ParseFile( - scriptFileInfo, - out Token[] tokens, - out ParseError[] errors); - - if (errors.Length > 0 && !String.Equals(errors[0].ErrorId, "WorkflowNotSupportedInPowerShellCore", StringComparison.OrdinalIgnoreCase)) - { - var message = String.Format("Could not parse '{0}' as a PowerShell script file.", scriptFileInfo); - var ex = new ArgumentException(message); - var psScriptFileParseError = new ErrorRecord(ex, "psScriptFileParseError", ErrorCategory.ParserError, null); - cmdletPassedIn.WriteError(psScriptFileParseError); - return successfullyParsed; - } - else if (ast != null) - { - // TODO: Anam do we still need to check if ast is not null? - // Get the block/group comment beginning with <#PSScriptInfo - List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); - string commentPattern = "<#PSScriptInfo"; - Regex rg = new Regex(commentPattern); - List psScriptInfoCommentTokens = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).ToList(); - - if (psScriptInfoCommentTokens.Count() == 0 || psScriptInfoCommentTokens[0] == null) - { - // TODO: Anam change to error from V2 - var message = String.Format("PSScriptInfo comment was missing or could not be parsed"); - var ex = new ArgumentException(message); - var psCommentParseError = new ErrorRecord(ex, "psScriptInfoCommentParseError", ErrorCategory.ParserError, null); - cmdletPassedIn.WriteError(psCommentParseError); - return successfullyParsed; - } - - string[] commentLines = Regex.Split(psScriptInfoCommentTokens[0].Text, "[\r\n]"); - // TODO: Anam clean above up as we don't need to store empty lines for CF and newline, I think? - string keyName = String.Empty; - string value = 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 + <#PSScriptInfo #> - */ - - /** - If comment line count is not more than two, it doesn't have the any metadata property - comment block would look like: - - <#PSScriptInfo - #> - */ - cmdletPassedIn.WriteVerbose("total comment lines: " + commentLines.Count()); - if (commentLines.Count() > 2) - { - // TODO: Anam is it an error if the metadata property is empty? - for (int i = 1; i < commentLines.Count(); i++) + */ + cmdletPassedIn.WriteVerbose("total comment lines: " + commentLines.Count()); + if (commentLines.Count() > 2) { - string line = commentLines[i]; - cmdletPassedIn.WriteVerbose("i: " + i + "line: " + line); - if (String.IsNullOrEmpty(line)) + // TODO: Anam is it an error if the metadata property is empty? + for (int i = 1; i < commentLines.Count(); i++) { - continue; + string line = commentLines[i]; + cmdletPassedIn.WriteVerbose("i: " + i + "line: " + line); + if (String.IsNullOrEmpty(line)) + { + continue; + } + // A line is starting with . conveys a new metadata property + // __NEWLINE__ is used for replacing the value lines while adding the value to $PSScriptInfo object + if (line.Trim().StartsWith(".")) + { + // string partPattern = "[.\s+]"; + string[] parts = line.Trim().TrimStart('.').Split(); + keyName = parts[0]; + value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; + parsedPSScriptInfoHashtable.Add(keyName, value); + } } - // A line is starting with . conveys a new metadata property - // __NEWLINE__ is used for replacing the value lines while adding the value to $PSScriptInfo object - if (line.Trim().StartsWith(".")) + successfullyParsed = true; + } + + // get .DESCRIPTION comment + CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); + if (scriptCommentInfo != null && !String.IsNullOrEmpty(scriptCommentInfo.Description)) + { + parsedPSScriptInfoHashtable.Add("DESCRIPTION", scriptCommentInfo.Description); + } + + // get RequiredModules + ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; + if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) + { + ReadOnlyCollection parsedRequiredModules = parsedScriptRequirements.RequiredModules; + parsedPSScriptInfoHashtable.Add("RequiredModules", parsedRequiredModules); + } + + // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow + // TODO: Anam DefinedWorkflow is no longer supported as of PowerShellCore 6+, do we ignore error or reject script? + // List parsedFunctionAst = ast.FindAll(a => a.GetType().Name == "FunctionDefinitionAst", true).ToList(); + // foreach (var p in parsedFunctionAst) + // { + // Console.WriteLine(p.Parent.Extent.Text); + // p. + // } + var parsedFunctionAst = ast.FindAll(a => a is FunctionDefinitionAst, true); + List allCommands = new List(); + if (allCommands.Count() > 0) + { + foreach (var function in parsedFunctionAst) { - // string partPattern = "[.\s+]"; - string[] parts = line.Trim().TrimStart('.').Split(); - keyName = parts[0]; - value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; - parsedPSScriptInfoHashtable.Add(keyName, value); + allCommands.Add((FunctionDefinitionAst) function); } + + List allCommandNames = allCommands.Select(a => a.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedCommands", allCommandNames); + + // b.Body.Extent.Text to get actual content. So "Function b.Name c.Body.Extent.Text" is that whole line lol + List allFunctionNames = allCommands.Where(a => !a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedFunctions", allFunctionNames); + + List allWorkflowNames = allCommands.Where(a => a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); } - successfullyParsed = true; } + } - // get .DESCRIPTION comment - CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); - if (scriptCommentInfo != null && !String.IsNullOrEmpty(scriptCommentInfo.Description)) - { - parsedPSScriptInfoHashtable.Add("DESCRIPTION", scriptCommentInfo.Description); - } + return successfullyParsed; + } - // get RequiredModules - ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; - if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) - { - ReadOnlyCollection parsedRequiredModules = parsedScriptRequirements.RequiredModules; - parsedPSScriptInfoHashtable.Add("RequiredModules", parsedRequiredModules); - } + #endregion - // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow - // TODO: Anam DefinedWorkflow is no longer supported as of PowerShellCore 6+, do we ignore error or reject script? - // List parsedFunctionAst = ast.FindAll(a => a.GetType().Name == "FunctionDefinitionAst", true).ToList(); - // foreach (var p in parsedFunctionAst) - // { - // Console.WriteLine(p.Parent.Extent.Text); - // p. - // } - var parsedFunctionAst = ast.FindAll(a => a is FunctionDefinitionAst, true); - List allCommands = new List(); - if (allCommands.Count() > 0) - { - foreach (var function in parsedFunctionAst) - { - allCommands.Add((FunctionDefinitionAst) function); - } + #region Public Methods + public bool TryCreateScriptFileInfoString( + out string PSScriptFileString + ) + { + PSScriptFileString = String.Empty; + bool fileContentsSuccessfullyCreated = false; - List allCommandNames = allCommands.Select(a => a.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedCommands", allCommandNames); - // b.Body.Extent.Text to get actual content. So "Function b.Name c.Body.Extent.Text" is that whole line lol - List allFunctionNames = allCommands.Where(a => !a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedFunctions", allFunctionNames); + return fileContentsSuccessfullyCreated; + } - List allWorkflowNames = allCommands.Where(a => a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); - } + public bool TryCreatePSScriptInfoString( + out string pSScriptInfoString + ) + { + bool pSScriptInfoSuccessfullyCreated = false; + pSScriptInfoString = String.Empty; + if (String.IsNullOrEmpty(Author) || String.IsNullOrEmpty(Description) || Version == null) + { + // write/throw error? + return pSScriptInfoSuccessfullyCreated; } + + 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}", String.Join("\n", ReleaseNotes))); + psScriptInfoLines.Add("#>"); + + pSScriptInfoString = String.Join("\n\n", psScriptInfoLines); + pSScriptInfoSuccessfullyCreated = true; + return pSScriptInfoSuccessfullyCreated; } + #endregion - return successfullyParsed; } - - #endregion -} +} \ No newline at end of file From 6851f0946756a43157f3827e16d9c9e473be9110 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Sun, 6 Feb 2022 23:50:01 -0500 Subject: [PATCH 10/86] RequiresString and CommentHelp string now print --- src/code/NewPSScriptFileInfo.cs | 33 +++++- src/code/PSScriptFileInfo.cs | 178 +++++++++++++++++++++++++++++++- src/code/Utils.cs | 175 +++++++++++++++++++++++++++++++ 3 files changed, 380 insertions(+), 6 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 24b52226f..d0b9a5023 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -1,13 +1,14 @@ -using System.Runtime.CompilerServices; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Microsoft.PowerShell.PowerShellGet.UtilClasses; -using NuGet.Versioning; + using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Management.Automation; +using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { @@ -76,13 +77,21 @@ public sealed class NewPSScriptFileInfo : PSCmdlet [ValidateNotNullOrEmpty()] public string Copyright { get; set; } + // /// + // /// The list of modules required by the script + // /// TODO: in V2 this had type Object[] + // /// + // [Parameter] + // [ValidateNotNullOrEmpty()] + // public string[] RequiredModules { get; set; } + /// /// The list of modules required by the script /// TODO: in V2 this had type Object[] /// [Parameter] [ValidateNotNullOrEmpty()] - public string[] RequiredModules { get; set; } + public Hashtable[] RequiredModules { get; set; } /// /// The list of external module dependencies taken by this script @@ -216,6 +225,16 @@ protected override void ProcessRecord() WriteVerbose("past path validation stuff"); + List validatedModuleSpecs = new List(); + if (RequiredModules.Length > 0) + { + Utils.CreateModuleSpecification(RequiredModules, out validatedModuleSpecs, out ErrorRecord[] errors); + foreach (ErrorRecord err in errors) + { + WriteError(err); + } + } + PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( version: Version, guid: Guid, @@ -226,6 +245,7 @@ protected override void ProcessRecord() licenseUri: _licenseUri, projectUri: _projectUri, iconUri: _iconUri, + requiredModules: validatedModuleSpecs.ToArray(), externalModuleDependencies: ExternalModuleDependencies, requiredScripts: RequiredScripts, externalScriptDependencies: ExternalScriptDependencies, @@ -233,11 +253,16 @@ protected override void ProcessRecord() privateData: PrivateData, description: Description); - if (currentScriptInfo.TryCreatePSScriptInfoString(out string psScriptInfoString)) + if (currentScriptInfo.GetPSScriptInfoString(out string psScriptInfoString)) { WriteVerbose(psScriptInfoString); } + currentScriptInfo.GetRequiresString(out string psRequiresString); + if (!String.IsNullOrEmpty(psRequiresString)) + { + WriteVerbose(psRequiresString); + } // get PSScriptInfo string --> take PSScriptInfo comment keys (from params passed in) and validate and turn into string // get Requires string --> from params passed in // get CommentHelpInfo string --> from params passed in diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index dafc5eb7b..3a99a43d6 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -1,3 +1,4 @@ +using System.Reflection; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -10,6 +11,7 @@ using System.Text.RegularExpressions; using System.Linq; using System.Collections.ObjectModel; +using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -66,6 +68,22 @@ public sealed class PSScriptFileInfo /// public Uri IconUri { get; } + // /// + // /// The list of modules required by the script + // /// TODO: in V2 this had type Object[] + // /// + // [Parameter] + // [ValidateNotNullOrEmpty()] + // public string[] RequiredModules { get; set; } + + /// + /// The list of modules required by the script + /// Hashtable keys: GUID, MaxVersion, Name (Required), RequiredVersion, Version + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public ModuleSpecification[] RequiredModules { get; set; } + /// /// the list of external module dependencies for the script /// @@ -100,6 +118,70 @@ public sealed class PSScriptFileInfo [ValidateNotNullOrEmpty()] public string Description { get; set; } + // TODO: seem to be optional keys for help comment, where would they be passed in from? constructor/New-X cmdlet? + /// + /// The synopsis of the script + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string Synopsis { get; set; } + + /// + /// The example(s) relating to the script's usage + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string[] Example { get; set; } + + /// + /// The inputs to the script + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string[] Inputs { get; set; } + + /// + /// The outputs to the script + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string[] Outputs { get; set; } + + /// + /// The notes for the script + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string[] Notes { get; set; } + + /// + /// The links for the script + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string[] Links { get; set; } + + /// + /// TODO: what is this? + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string[] Component { get; set; } + + /// + /// TODO: what is this? + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string[] Role { get; set; } + + /// + /// TODO: what is this? + /// + [Parameter(Mandatory = true)] + [ValidateNotNullOrEmpty()] + public string[] Functionality { get; set; } + #endregion @@ -115,6 +197,7 @@ public PSScriptFileInfo( Uri licenseUri, Uri projectUri, Uri iconUri, + ModuleSpecification[] requiredModules, // TODO: in V2 this was Object[] string[] externalModuleDependencies, string[] requiredScripts, string[] externalScriptDependencies, @@ -136,6 +219,7 @@ string description LicenseUri = licenseUri; ProjectUri = projectUri; IconUri = iconUri; + RequiredModules = requiredModules; // TODO: ANAM need a default value? ExternalModuleDependencies = externalModuleDependencies ?? Utils.EmptyStrArray; RequiredScripts = requiredScripts ?? Utils.EmptyStrArray; ExternalScriptDependencies = externalScriptDependencies ?? Utils.EmptyStrArray; @@ -311,12 +395,23 @@ out string PSScriptFileString { PSScriptFileString = String.Empty; bool fileContentsSuccessfullyCreated = false; + if (!GetPSScriptInfoString(out string psScriptInfoCommentString)) + { + return fileContentsSuccessfullyCreated; + } + + PSScriptFileString = psScriptInfoCommentString; + GetRequiresString(out string psRequiresString); + if (!String.IsNullOrEmpty(psRequiresString)) + { + PSScriptFileString += psRequiresString; + } return fileContentsSuccessfullyCreated; } - public bool TryCreatePSScriptInfoString( + public bool GetPSScriptInfoString( out string pSScriptInfoString ) { @@ -329,7 +424,7 @@ out string pSScriptInfoString } List psScriptInfoLines = new List(); - psScriptInfoLines.Add("PSScriptInfo"); + 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)); @@ -349,6 +444,85 @@ out string pSScriptInfoString pSScriptInfoSuccessfullyCreated = true; return pSScriptInfoSuccessfullyCreated; } + + public 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("#>"); + psRequiresString = String.Join("\n", psRequiresLines); + // TODO: where does the GUID come in? + } + } + + public bool GetScriptCommentHelpInfo( + out string psHelpInfo + ) + { + psHelpInfo = String.Empty; + bool psHelpInfoSuccessfullyCreated = false; + List psHelpInfoLines = new List(); + psHelpInfoLines.Add("<#\n"); + psHelpInfoLines.Add(String.Format(".DESCRIPTION {0}", Description)); + if (!String.IsNullOrEmpty(Synopsis)) + { + psHelpInfoLines.Add(String.Format(".SYNOPSIS {0}", Synopsis)); + } + + foreach (string currentExample in Example) + { + psHelpInfoLines.Add(String.Format(".EXAMPLE {0}", currentExample)); + } + + foreach (string input in Inputs) + { + psHelpInfoLines.Add(String.Format(".INPUTS {0}", input)); + } + + foreach (string output in Outputs) + { + psHelpInfoLines.Add(String.Format(".OUTPUTS {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 {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))); + } + + psHelpInfoLines.Add("#>"); + return psHelpInfoSuccessfullyCreated; + } + #endregion } diff --git a/src/code/Utils.cs b/src/code/Utils.cs index c07c21e06..60952c14a 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -16,6 +16,7 @@ using System.Runtime.InteropServices; using System.Security; using System.Text.RegularExpressions; +using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -708,6 +709,8 @@ public static List GetAllInstallationPaths( private readonly static Version PSVersion6 = new Version(6, 0); private static void GetStandardPlatformPaths( + + PSCmdlet psCmdlet, out string myDocumentsPath, out string programFilesPath) @@ -970,6 +973,178 @@ public static Hashtable ConvertJsonToHashtable( return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; } + public static void CreateModuleSpecification( + Hashtable[] moduleSpecHashtables, + out List validatedModuleSpecs, + out ErrorRecord[] errors + ) + { + // bool successfullyCreatedModuleSpecs = false; + List errorList = new List(); + List moduleSpecsList = new List(); + + foreach(Hashtable moduleSpec in moduleSpecHashtables) + { + if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) + { + var exMessage = "RequiredModules Hashtable entry 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); + continue; + } + + // at this point it must contain ModuleName key. + string moduleSpecName = (string) moduleSpec["ModuleName"]; + ModuleSpecification currentModuleSpec = null; + if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) + { + // pass to ModuleSpecification(string) constructor + currentModuleSpec = new ModuleSpecification(moduleSpecName); + if (currentModuleSpec != null) + { + moduleSpecsList.Add(currentModuleSpec); + } + else + { + var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + var ex = new ArgumentException(exMessage); + var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + errorList.Add(ModuleSpecNotCreatedError); + } + } + else + { + // TODO: ANAM perhaps not else + string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; + string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; + string moduleSpecRequiredVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; + Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; // TODO: ANAM this can be the default + + 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); + } + + currentModuleSpec = new ModuleSpecification(moduleSpecHash); + if (currentModuleSpec != null) + { + moduleSpecsList.Add(currentModuleSpec); + } + else + { + var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + var ex = new ArgumentException(exMessage); + var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + errorList.Add(ModuleSpecNotCreatedError); + } + } + + // NuGetVersion moduleSpecMaxVersion = null; + // NuGetVersion moduleSpecModuleVersion = null; + // NuGetVersion moduleSpecRequiredVersion = null; + + // // validate the module version, maximum version, required version are valid versions + // if (!String.IsNullOrEmpty(moduleSpecMaxVersionString)) + // { + // bool parsedSuccessfully = NuGetVersion.TryParse(moduleSpecMaxVersionString, out moduleSpecMaxVersion); + // if (!parsedSuccessfully) + // { + // var exMessage = "RequiredModules Hashtable entry had a key 'MaximumVersion' but value is not a valid NuGetVersion"; + // var ex = new ArgumentException(exMessage); + // var MaxVersionNotValidVersionInModuleSpecError = new ErrorRecord(ex, "MaxVersionNotValidVersionInModuleSpecification", ErrorCategory.InvalidArgument, null); + // errorList.Add(MaxVersionNotValidVersionInModuleSpecError); + // continue; + // } + // } + + // if (!String.IsNullOrEmpty(moduleSpecModuleVersionString)) + // { + // bool parsedSuccessfully = NuGetVersion.TryParse(moduleSpecMaxVersionString, out moduleSpecModuleVersion); + // if (!parsedSuccessfully) + // { + // var exMessage = "RequiredModules Hashtable entry had a key 'ModuleVersion' but value is not a valid NuGetVersion"; + // var ex = new ArgumentException(exMessage); + // var ModuleVersionNotValidVersionInModuleSpecError = new ErrorRecord(ex, "ModuleVersionNotValidVersionInModuleSpecification", ErrorCategory.InvalidArgument, null); + // errorList.Add(ModuleVersionNotValidVersionInModuleSpecError); + // continue; + // } + // } + + // if (!String.IsNullOrEmpty(moduleSpecRequiredVersionString)) + // { + // bool parsedSuccessfully = NuGetVersion.TryParse(moduleSpecMaxVersionString, out moduleSpecRequiredVersion); + // if (!parsedSuccessfully) + // { + // var exMessage = "RequiredModules Hashtable entry had a key 'RequiredVersion' but value is not a valid NuGetVersion"; + // var ex = new ArgumentException(exMessage); + // var RequiredVersionNotValidVersionInModuleSpecError = new ErrorRecord(ex, "RequiredVersionNotValidVersionInModuleSpecification", ErrorCategory.InvalidArgument, null); + // errorList.Add(RequiredVersionNotValidVersionInModuleSpecError); + // continue; + // } + // } + + // Hashtable moduleSpecHash = new Hashtable(); + + // moduleSpecHash.Add("ModuleName", moduleSpecName); + // if (moduleSpecGuid != Guid.Empty) + // { + // moduleSpecHash.Add("Guid", moduleSpecGuid); + // } + + // if (moduleSpecMaxVersion != null) + // { + // moduleSpecHash.Add("MaximumVersion", moduleSpecMaxVersion.ToString()); + // } + + // if (moduleSpecModuleVersion != null) + // { + // moduleSpecHash.Add("ModuleVersion", moduleSpecModuleVersion.ToString()); + // } + + // if (moduleSpecRequiredVersion != null) + // { + // moduleSpecHash.Add("RequiredVersion", moduleSpecRequiredVersion.ToString()); + // } + + // ModuleSpecification currentModuleSpec = new ModuleSpecification(moduleSpecHash); + // if (currentModuleSpec != null) + // { + // moduleSpecsList.Add(currentModuleSpec); + // } + // else + // { + // var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + // var ex = new ArgumentException(exMessage); + // var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + // errorList.Add(ModuleSpecNotCreatedError); + // } + + } + + errors = errorList.ToArray(); + validatedModuleSpecs = moduleSpecsList; + // return successfullyCreatedModuleSpecs; + } + #endregion #region Directory and File From 653d47ca4c43735896f88e0c75450d59d0cc0e2f Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 7 Feb 2022 00:14:55 -0500 Subject: [PATCH 11/86] move moduleSpecification parsing code from Utils to PSScriptFileInfo class --- src/code/NewPSScriptFileInfo.cs | 45 +++--- src/code/PSScriptFileInfo.cs | 111 +++++++++++++- src/code/Utils.cs | 261 +++++++++++--------------------- 3 files changed, 225 insertions(+), 192 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index d0b9a5023..8100897d8 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -225,15 +225,15 @@ protected override void ProcessRecord() WriteVerbose("past path validation stuff"); - List validatedModuleSpecs = new List(); - if (RequiredModules.Length > 0) - { - Utils.CreateModuleSpecification(RequiredModules, out validatedModuleSpecs, out ErrorRecord[] errors); - foreach (ErrorRecord err in errors) - { - WriteError(err); - } - } + // List validatedModuleSpecs = new List(); + // if (RequiredModules.Length > 0) + // { + // Utils.CreateModuleSpecification(RequiredModules, out validatedModuleSpecs, out ErrorRecord[] errors); + // foreach (ErrorRecord err in errors) + // { + // WriteError(err); + // } + // } PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( version: Version, @@ -245,24 +245,33 @@ protected override void ProcessRecord() licenseUri: _licenseUri, projectUri: _projectUri, iconUri: _iconUri, - requiredModules: validatedModuleSpecs.ToArray(), + // requiredModules: validatedModuleSpecs.ToArray(), + requiredModules: RequiredModules, externalModuleDependencies: ExternalModuleDependencies, requiredScripts: RequiredScripts, externalScriptDependencies: ExternalScriptDependencies, releaseNotes: ReleaseNotes, privateData: PrivateData, - description: Description); + description: Description, + cmdletPassedIn: this); - if (currentScriptInfo.GetPSScriptInfoString(out string psScriptInfoString)) - { - WriteVerbose(psScriptInfoString); - } + // if (currentScriptInfo.GetPSScriptInfoString(out string psScriptInfoString)) + // { + // WriteVerbose(psScriptInfoString); + // } + + // currentScriptInfo.GetRequiresString(out string psRequiresString); + // if (!String.IsNullOrEmpty(psRequiresString)) + // { + // WriteVerbose(psRequiresString); + // } - currentScriptInfo.GetRequiresString(out string psRequiresString); - if (!String.IsNullOrEmpty(psRequiresString)) + if (!currentScriptInfo.TryCreateScriptFileInfoString(out string psScriptFileContents)) { - WriteVerbose(psRequiresString); + // TODO: error handle } + + WriteVerbose(String.Format("\n{0}", psScriptFileContents)); // get PSScriptInfo string --> take PSScriptInfo comment keys (from params passed in) and validate and turn into string // get Requires string --> from params passed in // get CommentHelpInfo string --> from params passed in diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 3a99a43d6..63be02af1 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -197,19 +197,32 @@ public PSScriptFileInfo( Uri licenseUri, Uri projectUri, Uri iconUri, - ModuleSpecification[] requiredModules, // TODO: in V2 this was Object[] + // ModuleSpecification[] requiredModules, // TODO: in V2 this was Object[] + Hashtable[] requiredModules, string[] externalModuleDependencies, string[] requiredScripts, string[] externalScriptDependencies, string[] releaseNotes, string privateData, - string description + string description, + PSCmdlet cmdletPassedIn ) { if (String.IsNullOrEmpty(author)) { author = Environment.UserName; } + + List validatedModuleSpecs = new List(); + if (requiredModules.Length > 0) + { + CreateModuleSpecification(requiredModules, out validatedModuleSpecs, out ErrorRecord[] errors); + foreach (ErrorRecord err in errors) + { + cmdletPassedIn.WriteError(err); + } + } + Version = !String.IsNullOrEmpty(version) ? new Version (version) : new Version("1.0.0.0"); Guid = (guid == null || guid == Guid.Empty) ? new Guid() : guid; Author = !String.IsNullOrEmpty(author) ? author : Environment.UserName; @@ -219,7 +232,7 @@ string description LicenseUri = licenseUri; ProjectUri = projectUri; IconUri = iconUri; - RequiredModules = requiredModules; // TODO: ANAM need a default value? + RequiredModules = validatedModuleSpecs.ToArray(); // TODO: ANAM need a default value? ExternalModuleDependencies = externalModuleDependencies ?? Utils.EmptyStrArray; RequiredScripts = requiredScripts ?? Utils.EmptyStrArray; ExternalScriptDependencies = externalScriptDependencies ?? Utils.EmptyStrArray; @@ -405,6 +418,7 @@ out string PSScriptFileString GetRequiresString(out string psRequiresString); if (!String.IsNullOrEmpty(psRequiresString)) { + PSScriptFileString += "\n"; PSScriptFileString += psRequiresString; } @@ -523,6 +537,97 @@ out string psHelpInfo return psHelpInfoSuccessfullyCreated; } + public static void CreateModuleSpecification( + Hashtable[] moduleSpecHashtables, + out List validatedModuleSpecs, + out ErrorRecord[] errors + ) + { + // bool successfullyCreatedModuleSpecs = false; + List errorList = new List(); + List moduleSpecsList = new List(); + + foreach(Hashtable moduleSpec in moduleSpecHashtables) + { + if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) + { + var exMessage = "RequiredModules Hashtable entry 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); + continue; + } + + // at this point it must contain ModuleName key. + string moduleSpecName = (string) moduleSpec["ModuleName"]; + ModuleSpecification currentModuleSpec = null; + if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) + { + // pass to ModuleSpecification(string) constructor + currentModuleSpec = new ModuleSpecification(moduleSpecName); + if (currentModuleSpec != null) + { + moduleSpecsList.Add(currentModuleSpec); + } + else + { + var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + var ex = new ArgumentException(exMessage); + var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + errorList.Add(ModuleSpecNotCreatedError); + } + } + else + { + // TODO: ANAM perhaps not else + string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; + string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; + string moduleSpecRequiredVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; + Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; // TODO: ANAM this can be the default + + 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); + } + + currentModuleSpec = new ModuleSpecification(moduleSpecHash); + if (currentModuleSpec != null) + { + moduleSpecsList.Add(currentModuleSpec); + } + else + { + var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + var ex = new ArgumentException(exMessage); + var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + errorList.Add(ModuleSpecNotCreatedError); + } + } + } + + errors = errorList.ToArray(); + validatedModuleSpecs = moduleSpecsList; + // return successfullyCreatedModuleSpecs; + } + #endregion } diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 60952c14a..f0b76b3dc 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -973,177 +973,96 @@ public static Hashtable ConvertJsonToHashtable( return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; } - public static void CreateModuleSpecification( - Hashtable[] moduleSpecHashtables, - out List validatedModuleSpecs, - out ErrorRecord[] errors - ) - { - // bool successfullyCreatedModuleSpecs = false; - List errorList = new List(); - List moduleSpecsList = new List(); - - foreach(Hashtable moduleSpec in moduleSpecHashtables) - { - if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) - { - var exMessage = "RequiredModules Hashtable entry 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); - continue; - } - - // at this point it must contain ModuleName key. - string moduleSpecName = (string) moduleSpec["ModuleName"]; - ModuleSpecification currentModuleSpec = null; - if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) - { - // pass to ModuleSpecification(string) constructor - currentModuleSpec = new ModuleSpecification(moduleSpecName); - if (currentModuleSpec != null) - { - moduleSpecsList.Add(currentModuleSpec); - } - else - { - var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); - var ex = new ArgumentException(exMessage); - var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); - errorList.Add(ModuleSpecNotCreatedError); - } - } - else - { - // TODO: ANAM perhaps not else - string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; - string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; - string moduleSpecRequiredVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; - Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; // TODO: ANAM this can be the default - - 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); - } - - currentModuleSpec = new ModuleSpecification(moduleSpecHash); - if (currentModuleSpec != null) - { - moduleSpecsList.Add(currentModuleSpec); - } - else - { - var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); - var ex = new ArgumentException(exMessage); - var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); - errorList.Add(ModuleSpecNotCreatedError); - } - } - - // NuGetVersion moduleSpecMaxVersion = null; - // NuGetVersion moduleSpecModuleVersion = null; - // NuGetVersion moduleSpecRequiredVersion = null; - - // // validate the module version, maximum version, required version are valid versions - // if (!String.IsNullOrEmpty(moduleSpecMaxVersionString)) - // { - // bool parsedSuccessfully = NuGetVersion.TryParse(moduleSpecMaxVersionString, out moduleSpecMaxVersion); - // if (!parsedSuccessfully) - // { - // var exMessage = "RequiredModules Hashtable entry had a key 'MaximumVersion' but value is not a valid NuGetVersion"; - // var ex = new ArgumentException(exMessage); - // var MaxVersionNotValidVersionInModuleSpecError = new ErrorRecord(ex, "MaxVersionNotValidVersionInModuleSpecification", ErrorCategory.InvalidArgument, null); - // errorList.Add(MaxVersionNotValidVersionInModuleSpecError); - // continue; - // } - // } - - // if (!String.IsNullOrEmpty(moduleSpecModuleVersionString)) - // { - // bool parsedSuccessfully = NuGetVersion.TryParse(moduleSpecMaxVersionString, out moduleSpecModuleVersion); - // if (!parsedSuccessfully) - // { - // var exMessage = "RequiredModules Hashtable entry had a key 'ModuleVersion' but value is not a valid NuGetVersion"; - // var ex = new ArgumentException(exMessage); - // var ModuleVersionNotValidVersionInModuleSpecError = new ErrorRecord(ex, "ModuleVersionNotValidVersionInModuleSpecification", ErrorCategory.InvalidArgument, null); - // errorList.Add(ModuleVersionNotValidVersionInModuleSpecError); - // continue; - // } - // } - - // if (!String.IsNullOrEmpty(moduleSpecRequiredVersionString)) - // { - // bool parsedSuccessfully = NuGetVersion.TryParse(moduleSpecMaxVersionString, out moduleSpecRequiredVersion); - // if (!parsedSuccessfully) - // { - // var exMessage = "RequiredModules Hashtable entry had a key 'RequiredVersion' but value is not a valid NuGetVersion"; - // var ex = new ArgumentException(exMessage); - // var RequiredVersionNotValidVersionInModuleSpecError = new ErrorRecord(ex, "RequiredVersionNotValidVersionInModuleSpecification", ErrorCategory.InvalidArgument, null); - // errorList.Add(RequiredVersionNotValidVersionInModuleSpecError); - // continue; - // } - // } - - // Hashtable moduleSpecHash = new Hashtable(); - - // moduleSpecHash.Add("ModuleName", moduleSpecName); - // if (moduleSpecGuid != Guid.Empty) - // { - // moduleSpecHash.Add("Guid", moduleSpecGuid); - // } - - // if (moduleSpecMaxVersion != null) - // { - // moduleSpecHash.Add("MaximumVersion", moduleSpecMaxVersion.ToString()); - // } - - // if (moduleSpecModuleVersion != null) - // { - // moduleSpecHash.Add("ModuleVersion", moduleSpecModuleVersion.ToString()); - // } - - // if (moduleSpecRequiredVersion != null) - // { - // moduleSpecHash.Add("RequiredVersion", moduleSpecRequiredVersion.ToString()); - // } - - // ModuleSpecification currentModuleSpec = new ModuleSpecification(moduleSpecHash); - // if (currentModuleSpec != null) - // { - // moduleSpecsList.Add(currentModuleSpec); - // } - // else - // { - // var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); - // var ex = new ArgumentException(exMessage); - // var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); - // errorList.Add(ModuleSpecNotCreatedError); - // } - - } - - errors = errorList.ToArray(); - validatedModuleSpecs = moduleSpecsList; - // return successfullyCreatedModuleSpecs; - } + // public static void CreateModuleSpecification( + // Hashtable[] moduleSpecHashtables, + // out List validatedModuleSpecs, + // out ErrorRecord[] errors + // ) + // { + // // bool successfullyCreatedModuleSpecs = false; + // List errorList = new List(); + // List moduleSpecsList = new List(); + + // foreach(Hashtable moduleSpec in moduleSpecHashtables) + // { + // if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) + // { + // var exMessage = "RequiredModules Hashtable entry 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); + // continue; + // } + + // // at this point it must contain ModuleName key. + // string moduleSpecName = (string) moduleSpec["ModuleName"]; + // ModuleSpecification currentModuleSpec = null; + // if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) + // { + // // pass to ModuleSpecification(string) constructor + // currentModuleSpec = new ModuleSpecification(moduleSpecName); + // if (currentModuleSpec != null) + // { + // moduleSpecsList.Add(currentModuleSpec); + // } + // else + // { + // var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + // var ex = new ArgumentException(exMessage); + // var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + // errorList.Add(ModuleSpecNotCreatedError); + // } + // } + // else + // { + // // TODO: ANAM perhaps not else + // string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; + // string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; + // string moduleSpecRequiredVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; + // Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; // TODO: ANAM this can be the default + + // 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); + // } + + // currentModuleSpec = new ModuleSpecification(moduleSpecHash); + // if (currentModuleSpec != null) + // { + // moduleSpecsList.Add(currentModuleSpec); + // } + // else + // { + // var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + // var ex = new ArgumentException(exMessage); + // var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + // errorList.Add(ModuleSpecNotCreatedError); + // } + // } + // } + + // errors = errorList.ToArray(); + // validatedModuleSpecs = moduleSpecsList; + // // return successfullyCreatedModuleSpecs; + // } #endregion From da9eb7535596d834570e1348a340b0f88336852e Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 7 Feb 2022 01:20:18 -0500 Subject: [PATCH 12/86] commentHelpInfo string works properly now --- src/code/PSScriptFileInfo.cs | 105 +++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 63be02af1..fce9378ff 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -82,27 +82,27 @@ public sealed class PSScriptFileInfo /// [Parameter] [ValidateNotNullOrEmpty()] - public ModuleSpecification[] RequiredModules { get; set; } + public ModuleSpecification[] RequiredModules { get; set; } = new ModuleSpecification[]{}; /// /// the list of external module dependencies for the script /// - public string[] ExternalModuleDependencies { get; } + public string[] ExternalModuleDependencies { get; } = new string[]{}; /// /// the list of required scripts for the parent script /// - public string[] RequiredScripts { get; } + public string[] RequiredScripts { get; } = new string[]{}; /// /// the list of external script dependencies for the script /// - public string[] ExternalScriptDependencies { get; } + public string[] ExternalScriptDependencies { get; } = new string[]{}; /// /// the release notes relating to the script /// - public string[] ReleaseNotes { get; } + public string[] ReleaseNotes { get; } = new string[]{}; /// /// The private data associated with the script @@ -131,56 +131,56 @@ public sealed class PSScriptFileInfo /// [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty()] - public string[] Example { get; set; } + public string[] Example { get; set; } = new string[]{}; /// /// The inputs to the script /// [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty()] - public string[] Inputs { get; set; } + public string[] Inputs { get; set; } = new string[]{}; /// /// The outputs to the script /// [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty()] - public string[] Outputs { get; set; } + public string[] Outputs { get; set; } = new string[]{}; /// /// The notes for the script /// [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty()] - public string[] Notes { get; set; } + public string[] Notes { get; set; } = new string[]{}; /// /// The links for the script /// [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty()] - public string[] Links { get; set; } + public string[] Links { get; set; } = new string[]{}; /// /// TODO: what is this? /// [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty()] - public string[] Component { get; set; } + public string[] Component { get; set; } = new string[]{}; /// /// TODO: what is this? /// [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty()] - public string[] Role { get; set; } + public string[] Role { get; set; } = new string[]{}; /// /// TODO: what is this? /// [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty()] - public string[] Functionality { get; set; } + public string[] Functionality { get; set; } = new string[]{}; #endregion @@ -422,6 +422,18 @@ out string PSScriptFileString PSScriptFileString += psRequiresString; } + if (!GetScriptCommentHelpInfo(out string psHelpInfo)) + { + Console.WriteLine("GetScriptCommentHelpInfo returned false"); + return fileContentsSuccessfullyCreated; + } + else + { + Console.WriteLine("GetScriptCommentHelpInfo returned true"); + PSScriptFileString += "\n"; + PSScriptFileString += psHelpInfo; + } + return fileContentsSuccessfullyCreated; } @@ -486,8 +498,17 @@ out string psHelpInfo psHelpInfo = String.Empty; bool psHelpInfoSuccessfullyCreated = false; List psHelpInfoLines = new List(); + if (String.IsNullOrEmpty(Description)) + { + Console.WriteLine("Description was null or empty?"); + return psHelpInfoSuccessfullyCreated; + } + + psHelpInfoSuccessfullyCreated = true; + psHelpInfoLines.Add("<#\n"); psHelpInfoLines.Add(String.Format(".DESCRIPTION {0}", Description)); + if (!String.IsNullOrEmpty(Synopsis)) { psHelpInfoLines.Add(String.Format(".SYNOPSIS {0}", Synopsis)); @@ -498,42 +519,44 @@ out string psHelpInfo psHelpInfoLines.Add(String.Format(".EXAMPLE {0}", currentExample)); } - foreach (string input in Inputs) - { - psHelpInfoLines.Add(String.Format(".INPUTS {0}", input)); - } - foreach (string output in Outputs) - { - psHelpInfoLines.Add(String.Format(".OUTPUTS {0}", output)); - } + // foreach (string input in Inputs) + // { + // psHelpInfoLines.Add(String.Format(".INPUTS {0}", input)); + // } - if (Notes.Length > 0) - { - psHelpInfoLines.Add(String.Format(".NOTES\n{0}", String.Join("\n", Notes))); - } + // foreach (string output in Outputs) + // { + // psHelpInfoLines.Add(String.Format(".OUTPUTS {0}", output)); + // } - foreach (string link in Links) - { - psHelpInfoLines.Add(String.Format(".LINK {0}", link)); - } + // if (Notes.Length > 0) + // { + // psHelpInfoLines.Add(String.Format(".NOTES\n{0}", String.Join("\n", Notes))); + // } - if (Component.Length > 0) - { - psHelpInfoLines.Add(String.Format(".COMPONENT\n{0}", String.Join("\n", Component))); - } + // foreach (string link in Links) + // { + // psHelpInfoLines.Add(String.Format(".LINK {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 (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))); - } + // if (Functionality.Length > 0) + // { + // psHelpInfoLines.Add(String.Format(".FUNCTIONALITY\n{0}", String.Join("\n", Functionality))); + // } psHelpInfoLines.Add("#>"); + psHelpInfo = String.Join("\n", psHelpInfoLines); return psHelpInfoSuccessfullyCreated; } From b90caf82cf9eb1f5d3bf620397e6a8d129afa916 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 7 Feb 2022 01:20:50 -0500 Subject: [PATCH 13/86] fix typo --- src/code/PSScriptFileInfo.cs | 63 ++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index fce9378ff..9a12bf433 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -519,41 +519,40 @@ out string psHelpInfo psHelpInfoLines.Add(String.Format(".EXAMPLE {0}", currentExample)); } + foreach (string input in Inputs) + { + psHelpInfoLines.Add(String.Format(".INPUTS {0}", input)); + } + + foreach (string output in Outputs) + { + psHelpInfoLines.Add(String.Format(".OUTPUTS {0}", output)); + } - // foreach (string input in Inputs) - // { - // psHelpInfoLines.Add(String.Format(".INPUTS {0}", input)); - // } - - // foreach (string output in Outputs) - // { - // psHelpInfoLines.Add(String.Format(".OUTPUTS {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 {0}", link)); - // } - - // if (Component.Length > 0) - // { - // psHelpInfoLines.Add(String.Format(".COMPONENT\n{0}", String.Join("\n", Component))); - // } + 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 {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 (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))); - // } + if (Functionality.Length > 0) + { + psHelpInfoLines.Add(String.Format(".FUNCTIONALITY\n{0}", String.Join("\n", Functionality))); + } psHelpInfoLines.Add("#>"); psHelpInfo = String.Join("\n", psHelpInfoLines); From ddcab1aac98c064b0f2e4c368739ed4ed9bd2c13 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 7 Feb 2022 02:54:57 -0500 Subject: [PATCH 14/86] ensure Path and PassThru work --- src/code/NewPSScriptFileInfo.cs | 91 +++++++++++++++++++++++---------- src/code/PSScriptFileInfo.cs | 8 +-- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 8100897d8..7479171e7 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -1,3 +1,4 @@ +using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -203,24 +204,59 @@ protected override void ProcessRecord() WriteVerbose("past Uri validation"); - // determine whether script contents will be written out to .ps1 file or to console - // case 1: Path passed in. If path already exists Force required to overwrite. Else write error. - // case 2: no Path passed in. PassThru required to print script file contents to console. - if (!String.IsNullOrEmpty(Path) && !Path.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + // // determine whether script contents will be written out to .ps1 file or to console + // // case 1: Path passed in. If path already exists Force required to overwrite. Else write error. + // // case 2: no Path passed in. PassThru required to print script file contents to console. + // if (!String.IsNullOrEmpty(Path) && !Path.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); + // } + + // if (!String.IsNullOrEmpty(Path) && File.Exists(Path) && !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); + // } + + bool usePath = false; + if (!String.IsNullOrEmpty(Path)) { - 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); + if (!Path.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); + } + else if (File.Exists(Path) && !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); + } + + // if neither of those cases, Path is non-null and valid. + usePath = true; } - - if (!String.IsNullOrEmpty(Path) && File.Exists(Path) && !Force) + else if (PassThru) { - // .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."; + usePath = false; + } + else + { + // Either valid Path or PassThru parameter must be supplied. + var exMessage = "Either -Path parameter or -PassThru parameter value must be supplied to output script file contents to."; var ex = new ArgumentException(exMessage); - var ScriptAtPathAlreadyExistsError = new ErrorRecord(ex, "ScriptAtPathAlreadyExists", ErrorCategory.InvalidArgument, null); - ThrowTerminatingError(ScriptAtPathAlreadyExistsError); + var PathOrPassThruParameterRequiredError = new ErrorRecord(ex, "PathOrPassThruParameterRequired", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(PathOrPassThruParameterRequiredError); } WriteVerbose("past path validation stuff"); @@ -255,23 +291,24 @@ protected override void ProcessRecord() description: Description, cmdletPassedIn: this); - // if (currentScriptInfo.GetPSScriptInfoString(out string psScriptInfoString)) - // { - // WriteVerbose(psScriptInfoString); - // } - - // currentScriptInfo.GetRequiresString(out string psRequiresString); - // if (!String.IsNullOrEmpty(psRequiresString)) - // { - // WriteVerbose(psRequiresString); - // } - if (!currentScriptInfo.TryCreateScriptFileInfoString(out string psScriptFileContents)) { - // TODO: error handle + var exMessage = "Script file contents could not be created"; // TODO: Anam probably some error message here? + var ex = new ArgumentException(exMessage); + var ScriptContentCouldNotBeCreatedError = new ErrorRecord(ex, "ScriptContentCouldNotBeCreated", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(ScriptContentCouldNotBeCreatedError); } - WriteVerbose(String.Format("\n{0}", psScriptFileContents)); + if (usePath) + { + File.WriteAllText(Path, psScriptFileContents); // TODO: Anam better way to do this? + } + + if (!usePath || PassThru) + { + // TODO: Anam do we also write to console if Path AND PassThru used together? + WriteObject(psScriptFileContents); + } // get PSScriptInfo string --> take PSScriptInfo comment keys (from params passed in) and validate and turn into string // get Requires string --> from params passed in // get CommentHelpInfo string --> from params passed in diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 9a12bf433..d95c08f97 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -1,4 +1,3 @@ -using System.Reflection; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -224,7 +223,7 @@ PSCmdlet cmdletPassedIn } Version = !String.IsNullOrEmpty(version) ? new Version (version) : new Version("1.0.0.0"); - Guid = (guid == null || guid == Guid.Empty) ? new Guid() : guid; + Guid = (guid == null || guid == Guid.Empty) ? Guid.NewGuid() : guid; Author = !String.IsNullOrEmpty(author) ? author : Environment.UserName; CompanyName = companyName; Copyright = copyright; @@ -410,14 +409,17 @@ out string PSScriptFileString bool fileContentsSuccessfullyCreated = false; if (!GetPSScriptInfoString(out string psScriptInfoCommentString)) { + Console.WriteLine("PSScriptInfo returned false"); return fileContentsSuccessfullyCreated; } + fileContentsSuccessfullyCreated = true; PSScriptFileString = psScriptInfoCommentString; GetRequiresString(out string psRequiresString); if (!String.IsNullOrEmpty(psRequiresString)) { + Console.WriteLine("Requires wasn't empty upon return"); PSScriptFileString += "\n"; PSScriptFileString += psRequiresString; } @@ -425,7 +427,7 @@ out string PSScriptFileString if (!GetScriptCommentHelpInfo(out string psHelpInfo)) { Console.WriteLine("GetScriptCommentHelpInfo returned false"); - return fileContentsSuccessfullyCreated; + return false; } else { From cacf746bbfe7f3eb7f64883c12ef42dbe43e079c Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 7 Feb 2022 21:11:26 -0500 Subject: [PATCH 15/86] clean up code --- src/code/NewPSScriptFileInfo.cs | 71 ++++--------------------- src/code/PSScriptFileInfo.cs | 88 +++++++++++++++++++++---------- src/code/Utils.cs | 91 --------------------------------- 3 files changed, 71 insertions(+), 179 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 7479171e7..c55ebb414 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -78,17 +78,8 @@ public sealed class NewPSScriptFileInfo : PSCmdlet [ValidateNotNullOrEmpty()] public string Copyright { get; set; } - // /// - // /// The list of modules required by the script - // /// TODO: in V2 this had type Object[] - // /// - // [Parameter] - // [ValidateNotNullOrEmpty()] - // public string[] RequiredModules { get; set; } - /// /// The list of modules required by the script - /// TODO: in V2 this had type Object[] /// [Parameter] [ValidateNotNullOrEmpty()] @@ -176,7 +167,6 @@ public sealed class NewPSScriptFileInfo : PSCmdlet protected override void ProcessRecord() { - WriteVerbose("In Anam's cmdlet!"); // validate Uri related parameters passed in as strings if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUrl(uriString: ProjectUri, cmdletPassedIn: this, @@ -202,28 +192,6 @@ protected override void ProcessRecord() ThrowTerminatingError(iconErrorRecord); } - WriteVerbose("past Uri validation"); - - // // determine whether script contents will be written out to .ps1 file or to console - // // case 1: Path passed in. If path already exists Force required to overwrite. Else write error. - // // case 2: no Path passed in. PassThru required to print script file contents to console. - // if (!String.IsNullOrEmpty(Path) && !Path.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); - // } - - // if (!String.IsNullOrEmpty(Path) && File.Exists(Path) && !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); - // } - bool usePath = false; if (!String.IsNullOrEmpty(Path)) { @@ -259,18 +227,6 @@ protected override void ProcessRecord() ThrowTerminatingError(PathOrPassThruParameterRequiredError); } - WriteVerbose("past path validation stuff"); - - // List validatedModuleSpecs = new List(); - // if (RequiredModules.Length > 0) - // { - // Utils.CreateModuleSpecification(RequiredModules, out validatedModuleSpecs, out ErrorRecord[] errors); - // foreach (ErrorRecord err in errors) - // { - // WriteError(err); - // } - // } - PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( version: Version, guid: Guid, @@ -281,7 +237,6 @@ protected override void ProcessRecord() licenseUri: _licenseUri, projectUri: _projectUri, iconUri: _iconUri, - // requiredModules: validatedModuleSpecs.ToArray(), requiredModules: RequiredModules, externalModuleDependencies: ExternalModuleDependencies, requiredScripts: RequiredScripts, @@ -291,12 +246,17 @@ protected override void ProcessRecord() description: Description, cmdletPassedIn: this); - if (!currentScriptInfo.TryCreateScriptFileInfoString(out string psScriptFileContents)) + if (!currentScriptInfo.TryCreateScriptFileInfoString( + pSScriptFileString: out string psScriptFileContents, + errors: out ErrorRecord[] errors)) { - var exMessage = "Script file contents could not be created"; // TODO: Anam probably some error message here? - var ex = new ArgumentException(exMessage); - var ScriptContentCouldNotBeCreatedError = new ErrorRecord(ex, "ScriptContentCouldNotBeCreated", ErrorCategory.InvalidArgument, null); - ThrowTerminatingError(ScriptContentCouldNotBeCreatedError); + foreach (ErrorRecord err in errors) + { + WriteError(err); + } + + return; + // TODO: Anam, currently only one error and you return. So maybe this shouldn't be a list? But for extensability makes sense. } if (usePath) @@ -308,16 +268,7 @@ protected override void ProcessRecord() { // TODO: Anam do we also write to console if Path AND PassThru used together? WriteObject(psScriptFileContents); - } - // get PSScriptInfo string --> take PSScriptInfo comment keys (from params passed in) and validate and turn into string - // get Requires string --> from params passed in - // get CommentHelpInfo string --> from params passed in - - // commpose totalMetadata string, which contains PSScriptInfo string + Requires string + CommentHelpInfo string - - // write to file at path. Call Test-ScriptFileInfo. - // If PassThru write content of file - + } } #endregion diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index d95c08f97..eb39ce3ec 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -231,7 +231,7 @@ PSCmdlet cmdletPassedIn LicenseUri = licenseUri; ProjectUri = projectUri; IconUri = iconUri; - RequiredModules = validatedModuleSpecs.ToArray(); // TODO: ANAM need a default value? + RequiredModules = validatedModuleSpecs.ToArray() ?? new ModuleSpecification[]{}; ExternalModuleDependencies = externalModuleDependencies ?? Utils.EmptyStrArray; RequiredScripts = requiredScripts ?? Utils.EmptyStrArray; ExternalScriptDependencies = externalScriptDependencies ?? Utils.EmptyStrArray; @@ -402,55 +402,82 @@ Feature 5 #region Public Methods public bool TryCreateScriptFileInfoString( - out string PSScriptFileString + out string pSScriptFileString, + out ErrorRecord[] errors ) { - PSScriptFileString = String.Empty; + errors = new ErrorRecord[]{}; + List errorsList = new List(); + + pSScriptFileString = String.Empty; bool fileContentsSuccessfullyCreated = false; - if (!GetPSScriptInfoString(out string psScriptInfoCommentString)) + + // 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 scriptInfoError)) { - Console.WriteLine("PSScriptInfo returned false"); + if (scriptInfoError != null) + { + errorsList.Add(scriptInfoError); + errors = errorsList.ToArray(); + } + return fileContentsSuccessfullyCreated; } - fileContentsSuccessfullyCreated = true; - PSScriptFileString = psScriptInfoCommentString; + pSScriptFileString = psScriptInfoCommentString; - GetRequiresString(out string psRequiresString); + // 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)) { - Console.WriteLine("Requires wasn't empty upon return"); - PSScriptFileString += "\n"; - PSScriptFileString += psRequiresString; + pSScriptFileString += "\n"; + pSScriptFileString += psRequiresString; } - if (!GetScriptCommentHelpInfo(out string psHelpInfo)) - { - Console.WriteLine("GetScriptCommentHelpInfo returned false"); - return false; - } - else + // 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)) { - Console.WriteLine("GetScriptCommentHelpInfo returned true"); - PSScriptFileString += "\n"; - PSScriptFileString += psHelpInfo; + if (commentHelpInfoError != null) + { + errorsList.Add(commentHelpInfoError); + errors = errorsList.ToArray(); + } + + return fileContentsSuccessfullyCreated; } + pSScriptFileString += "\n" + psHelpInfo; + + fileContentsSuccessfullyCreated = true; return fileContentsSuccessfullyCreated; } public bool GetPSScriptInfoString( - out string pSScriptInfoString + out string pSScriptInfoString, + out ErrorRecord error ) { + error = null; bool pSScriptInfoSuccessfullyCreated = false; pSScriptInfoString = String.Empty; - if (String.IsNullOrEmpty(Author) || String.IsNullOrEmpty(Description) || Version == null) + + if (String.IsNullOrEmpty(Author) || Version == null) { - // write/throw error? + var exMessage = "PSScriptInfo must contain values for Author and Version. Ensure both of these are present."; + var ex = new ArgumentException(exMessage); + var PSScriptInfoMissingAuthorOrVersionError = new ErrorRecord(ex, "PSScriptInfoMissingAuthorOrVersion", ErrorCategory.InvalidArgument, null); + error = PSScriptInfoMissingAuthorOrVersionError; return pSScriptInfoSuccessfullyCreated; } + pSScriptInfoSuccessfullyCreated = true; List psScriptInfoLines = new List(); psScriptInfoLines.Add("<#PSScriptInfo"); psScriptInfoLines.Add(String.Format(".VERSION {0}", Version.ToString())); @@ -469,7 +496,6 @@ out string pSScriptInfoString psScriptInfoLines.Add("#>"); pSScriptInfoString = String.Join("\n\n", psScriptInfoLines); - pSScriptInfoSuccessfullyCreated = true; return pSScriptInfoSuccessfullyCreated; } @@ -487,27 +513,33 @@ out string psRequiresString { psRequiresLines.Add(String.Format("Requires -Module {0}", moduleSpec.ToString())); } + psRequiresLines.Add("#>"); psRequiresString = String.Join("\n", psRequiresLines); - // TODO: where does the GUID come in? + // TODO: ANAM where does the GUID come in? } } public bool GetScriptCommentHelpInfo( - out string psHelpInfo + out string psHelpInfo, + out ErrorRecord error ) { + error = null; psHelpInfo = String.Empty; bool psHelpInfoSuccessfullyCreated = false; List psHelpInfoLines = new List(); + if (String.IsNullOrEmpty(Description)) { - Console.WriteLine("Description was null or empty?"); + var exMessage = "PSScript file must contain value for Description. Ensure value for Description is passed in and try again."; + var ex = new ArgumentException(exMessage); + var PSScriptInfoMissingDescriptionError = new ErrorRecord(ex, "PSScriptInfoMissingDescription", ErrorCategory.InvalidArgument, null); + error = PSScriptInfoMissingDescriptionError; return psHelpInfoSuccessfullyCreated; } psHelpInfoSuccessfullyCreated = true; - psHelpInfoLines.Add("<#\n"); psHelpInfoLines.Add(String.Format(".DESCRIPTION {0}", Description)); diff --git a/src/code/Utils.cs b/src/code/Utils.cs index f0b76b3dc..ffd210eb1 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -973,97 +973,6 @@ public static Hashtable ConvertJsonToHashtable( return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; } - // public static void CreateModuleSpecification( - // Hashtable[] moduleSpecHashtables, - // out List validatedModuleSpecs, - // out ErrorRecord[] errors - // ) - // { - // // bool successfullyCreatedModuleSpecs = false; - // List errorList = new List(); - // List moduleSpecsList = new List(); - - // foreach(Hashtable moduleSpec in moduleSpecHashtables) - // { - // if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) - // { - // var exMessage = "RequiredModules Hashtable entry 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); - // continue; - // } - - // // at this point it must contain ModuleName key. - // string moduleSpecName = (string) moduleSpec["ModuleName"]; - // ModuleSpecification currentModuleSpec = null; - // if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) - // { - // // pass to ModuleSpecification(string) constructor - // currentModuleSpec = new ModuleSpecification(moduleSpecName); - // if (currentModuleSpec != null) - // { - // moduleSpecsList.Add(currentModuleSpec); - // } - // else - // { - // var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); - // var ex = new ArgumentException(exMessage); - // var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); - // errorList.Add(ModuleSpecNotCreatedError); - // } - // } - // else - // { - // // TODO: ANAM perhaps not else - // string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; - // string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; - // string moduleSpecRequiredVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; - // Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; // TODO: ANAM this can be the default - - // 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); - // } - - // currentModuleSpec = new ModuleSpecification(moduleSpecHash); - // if (currentModuleSpec != null) - // { - // moduleSpecsList.Add(currentModuleSpec); - // } - // else - // { - // var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); - // var ex = new ArgumentException(exMessage); - // var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); - // errorList.Add(ModuleSpecNotCreatedError); - // } - // } - // } - - // errors = errorList.ToArray(); - // validatedModuleSpecs = moduleSpecsList; - // // return successfullyCreatedModuleSpecs; - // } - #endregion #region Directory and File From 7f023cbdee7041801284d4169253b641548c1832 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 8 Feb 2022 21:12:16 -0500 Subject: [PATCH 16/86] test-scriptfileinfo now works; but PSScriptFileInfo constructor takes Hashtable[] RequiredModules --- src/PowerShellGet.psd1 | 3 +- src/code/NewPSScriptFileInfo.cs | 3 +- src/code/PSScriptFileInfo.cs | 194 ++++++++++++++------ src/code/TestPSScriptFileInfo.cs | 117 ++++++++++++ src/code/UpdatePSScriptFileInfo.cs | 277 +++++++++++++++++++++++++++++ src/code/Utils.cs | 11 ++ 6 files changed, 551 insertions(+), 54 deletions(-) create mode 100644 src/code/TestPSScriptFileInfo.cs create mode 100644 src/code/UpdatePSScriptFileInfo.cs diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index ee441d669..c3eb82d63 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -25,7 +25,8 @@ 'Uninstall-PSResource', 'Unregister-PSResourceRepository', 'Update-PSResource', - 'New-PSScriptFileInfo') + 'New-PSScriptFileInfo', + 'Test-PSScriptFileInfo') VariablesToExport = 'PSGetPath' AliasesToExport = @('inmo', 'fimo', 'upmo', 'pumo') diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index c55ebb414..21cdd7511 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -256,7 +256,8 @@ protected override void ProcessRecord() } return; - // TODO: Anam, currently only one error and you return. So maybe this shouldn't be a list? But for extensability makes sense. + // TODO: Anam, currently only one error and you return. So maybe this shouldn't be a list? + // But for extensability makes sense. } if (usePath) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index eb39ce3ec..40e9512f7 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -1,3 +1,4 @@ +using System.Reflection; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -242,14 +243,44 @@ PSCmdlet cmdletPassedIn #endregion + /** + 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 + #> + */ + #region Public Static Methods public static bool TryParseScriptFileInfo( string scriptFileInfo, - PSCmdlet cmdletPassedIn, - out Hashtable parsedPSScriptInfoHashtable) + out PSScriptFileInfo parsedScript, + out Hashtable parsedPSScriptInfoHashtable, + out ErrorRecord[] errors2, + PSCmdlet cmdletPassedIn) { + parsedScript = null; parsedPSScriptInfoHashtable = new Hashtable(); + errors2 = new ErrorRecord[]{}; + List errorsList = new List(); bool successfullyParsed = false; if (scriptFileInfo.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) @@ -258,19 +289,26 @@ public static bool TryParseScriptFileInfo( var ast = Parser.ParseFile( scriptFileInfo, out Token[] tokens, - out ParseError[] errors); + out ParseError[] parserErrors); - if (errors.Length > 0 && !String.Equals(errors[0].ErrorId, "WorkflowNotSupportedInPowerShellCore", StringComparison.OrdinalIgnoreCase)) + if (parserErrors.Length > 0 && !String.Equals(parserErrors[0].ErrorId, "WorkflowNotSupportedInPowerShellCore", StringComparison.OrdinalIgnoreCase)) { - var message = String.Format("Could not parse '{0}' as a PowerShell script file.", scriptFileInfo); - var ex = new ArgumentException(message); - var psScriptFileParseError = new ErrorRecord(ex, "psScriptFileParseError", ErrorCategory.ParserError, null); - cmdletPassedIn.WriteError(psScriptFileParseError); + // TODO: Anam - do we want to completely ignore WorkFlowNotSupportedInPowerShellCore error (not even write) + // or do we want to write, but not return if it's just that error? + foreach (ParseError err in parserErrors) + { + var message = String.Format("Could not parse '{0}' as a PowerShell script file due to {1}.", scriptFileInfo, err.Message); + var ex = new ArgumentException(message); + var psScriptFileParseError = new ErrorRecord(ex, err.ErrorId, ErrorCategory.ParserError, null); + errorsList.Add(psScriptFileParseError); + } + return successfullyParsed; } else if (ast != null) { // TODO: Anam do we still need to check if ast is not null? + // Get the block/group comment beginning with <#PSScriptInfo List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); string commentPattern = "<#PSScriptInfo"; @@ -279,44 +317,19 @@ public static bool TryParseScriptFileInfo( if (psScriptInfoCommentTokens.Count() == 0 || psScriptInfoCommentTokens[0] == null) { - // TODO: Anam change to error from V2 var message = String.Format("PSScriptInfo comment was missing or could not be parsed"); var ex = new ArgumentException(message); - var psCommentParseError = new ErrorRecord(ex, "psScriptInfoCommentParseError", ErrorCategory.ParserError, null); - cmdletPassedIn.WriteError(psCommentParseError); + var psCommentMissingError = new ErrorRecord(ex, "psScriptInfoCommentMissingError", ErrorCategory.ParserError, null); + errorsList.Add(psCommentMissingError); + + errors2 = errorsList.ToArray(); return successfullyParsed; } string[] commentLines = Regex.Split(psScriptInfoCommentTokens[0].Text, "[\r\n]"); - // TODO: Anam clean above up as we don't need to store empty lines for CF and newline, I think? string keyName = String.Empty; string value = 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 - #> - */ - /** If comment line count is not more than two, it doesn't have the any metadata property comment block would look like: @@ -324,55 +337,68 @@ Feature 5 <#PSScriptInfo #> */ - cmdletPassedIn.WriteVerbose("total comment lines: " + commentLines.Count()); + if (commentLines.Count() > 2) { // TODO: Anam is it an error if the metadata property is empty? for (int i = 1; i < commentLines.Count(); i++) { string line = commentLines[i]; - cmdletPassedIn.WriteVerbose("i: " + i + "line: " + line); if (String.IsNullOrEmpty(line)) { continue; } + // A line is starting with . conveys a new metadata property - // __NEWLINE__ is used for replacing the value lines while adding the value to $PSScriptInfo object if (line.Trim().StartsWith(".")) { - // string partPattern = "[.\s+]"; string[] parts = line.Trim().TrimStart('.').Split(); keyName = parts[0]; value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; parsedPSScriptInfoHashtable.Add(keyName, value); } } + successfullyParsed = true; } // get .DESCRIPTION comment CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); - if (scriptCommentInfo != null && !String.IsNullOrEmpty(scriptCommentInfo.Description)) + if (scriptCommentInfo != null) { - parsedPSScriptInfoHashtable.Add("DESCRIPTION", scriptCommentInfo.Description); + if (!String.IsNullOrEmpty(scriptCommentInfo.Description)) + { + parsedPSScriptInfoHashtable.Add("DESCRIPTION", scriptCommentInfo.Description); + } + else + { + var message = String.Format("PSScript is missing the required Description property"); + var ex = new ArgumentException(message); + var psScriptMissingDescriptionPropertyError = new ErrorRecord(ex, "psScriptDescriptionMissingDescription", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingDescriptionPropertyError); + return false; + } } // get RequiredModules ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; + ReadOnlyCollection parsedModules = null; + if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) { ReadOnlyCollection parsedRequiredModules = parsedScriptRequirements.RequiredModules; parsedPSScriptInfoHashtable.Add("RequiredModules", parsedRequiredModules); + Console.WriteLine(parsedRequiredModules.ToString()); + Console.WriteLine("and as a value:"); + Console.WriteLine((ReadOnlyCollection) parsedPSScriptInfoHashtable["RequiredModules"]); + if (parsedPSScriptInfoHashtable.ContainsKey("RequiredModules")) + { + parsedModules = (ReadOnlyCollection) parsedPSScriptInfoHashtable["RequiredModules"]; + } } // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow // TODO: Anam DefinedWorkflow is no longer supported as of PowerShellCore 6+, do we ignore error or reject script? - // List parsedFunctionAst = ast.FindAll(a => a.GetType().Name == "FunctionDefinitionAst", true).ToList(); - // foreach (var p in parsedFunctionAst) - // { - // Console.WriteLine(p.Parent.Extent.Text); - // p. - // } var parsedFunctionAst = ast.FindAll(a => a is FunctionDefinitionAst, true); List allCommands = new List(); if (allCommands.Count() > 0) @@ -392,6 +418,70 @@ Feature 5 List allWorkflowNames = allCommands.Where(a => a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); } + + + string parsedVersion = (string) parsedPSScriptInfoHashtable["VERSION"]; + string parsedAuthor = (string) parsedPSScriptInfoHashtable["AUTHOR"]; + Guid parsedGuid = String.IsNullOrEmpty((string)parsedPSScriptInfoHashtable["GUID"]) ? Guid.NewGuid() : new Guid((string) parsedPSScriptInfoHashtable["GUID"]); + if (!String.IsNullOrEmpty(parsedVersion) || !String.IsNullOrEmpty(parsedAuthor) || parsedGuid == Guid.Empty) + { + var message = String.Format("PSScript file is missing one of the following required properties: Version, Author, Guid"); + var ex = new ArgumentException(message); + var psScriptMissingRequiredPropertyError = new ErrorRecord(ex, "psScriptMissingRequiredProperty", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingRequiredPropertyError); + } + + try + { + char[] spaceDelimeter = new char[]{' '}; + string[] parsedTags = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["TAGS"]); + string[] parsedExternalModuleDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["EXTERNALMODULEDEPENDENCIES"]); + string[] parsedRequiredScripts = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["REQUIREDSCRIPTS"]); + string[] parsedExternalScriptDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["EXTERNALSCRIPTDEPENDENCIES"]); + string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["RELEASENOTES"]); + Uri parsedLicenseUri = new Uri((string) parsedPSScriptInfoHashtable["LICENSEURI"]); + Uri parsedProjectUri = new Uri((string) parsedPSScriptInfoHashtable["PROJECTURI"]); + Uri parsedIconUri = new Uri((string) parsedPSScriptInfoHashtable["ICONURI"]); + + + + + // TODO: Anam Hashtable should contain all keys, but values may be String.empty + parsedScript = new PSScriptFileInfo( + version: parsedVersion, + guid: parsedGuid, + author: parsedAuthor, + companyName: (string) parsedPSScriptInfoHashtable["COMPANYNAME"], + copyright: (string) parsedPSScriptInfoHashtable["COPYRIGHT"], + tags: parsedTags, + licenseUri: parsedLicenseUri, + projectUri: parsedProjectUri, + iconUri: parsedIconUri, + // ModuleSpecification[] requiredModules, // TODO: in V2 this was Object[] + requiredModules: new Hashtable[]{}, + externalModuleDependencies: parsedExternalModuleDependencies, + requiredScripts: parsedRequiredScripts, + externalScriptDependencies: parsedExternalScriptDependencies, + releaseNotes: parsedReleaseNotes, + privateData: (string) parsedPSScriptInfoHashtable["PRIVATEDATA"], + description: scriptCommentInfo.Description, + cmdletPassedIn: cmdletPassedIn); + } + 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); + } + } + else + { + var message = String.Format(".ps1 file was parsed but AST was null"); + var ex = new ArgumentException(message); + var astCouldNotBeCreatedError = new ErrorRecord(ex, "ASTCouldNotBeCreated", ErrorCategory.ParserError, null); + errorsList.Add(astCouldNotBeCreatedError); + return successfullyParsed; } } @@ -508,13 +598,13 @@ out string psRequiresString if (RequiredModules.Length > 0) { List psRequiresLines = new List(); - psRequiresLines.Add("<#\n"); + psRequiresLines.Add("\n"); foreach (ModuleSpecification moduleSpec in RequiredModules) { - psRequiresLines.Add(String.Format("Requires -Module {0}", moduleSpec.ToString())); + psRequiresLines.Add(String.Format("#Requires -Module {0}", moduleSpec.ToString())); } - psRequiresLines.Add("#>"); + psRequiresLines.Add("\n"); psRequiresString = String.Join("\n", psRequiresLines); // TODO: ANAM where does the GUID come in? } diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs new file mode 100644 index 000000000..8a06deac3 --- /dev/null +++ b/src/code/TestPSScriptFileInfo.cs @@ -0,0 +1,117 @@ +using System.Net; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.PowerShell.PowerShellGet.UtilClasses; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using Microsoft.PowerShell.Commands; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// It retrieves a resource that was installed with Install-PSResource + /// Returns a single resource or multiple resource. + /// + [Cmdlet(VerbsDiagnostic.Test, "PSScriptFileInfo")] + public sealed class TestPSScriptFileInfo : PSCmdlet + { + #region Parameters + + /// + /// The path to the .ps1 file to test + /// + [Parameter(Position = 0, Mandatory = true)] + [ValidateNotNullOrEmpty] + public string Path { get; set; } + + #endregion + + #region Methods + + protected override void ProcessRecord() + { + if (!File.Exists(Path)) + { + // .ps1 file at specified location already exists and Force parameter isn't used to rewrite the file + 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); + } + + if (!Path.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(Path); + 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 resolvedPath = resolvedPaths[0].Path; + + bool isValidPSScriptFile = PSScriptFileInfo.TryParseScriptFileInfo( + scriptFileInfo: resolvedPath, + parsedScript: out PSScriptFileInfo parsedScriptInfo, + errors2: out ErrorRecord[] errors, + parsedPSScriptInfoHashtable: out Hashtable parsedHash, + cmdletPassedIn: this); + + WriteVerbose("is valid: " + isValidPSScriptFile); + + + + + + + + // PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( + // version: Version, + // guid: Guid, + // author: Author, + // companyName: CompanyName, + // copyright: Copyright, + // tags: Tags, + // licenseUri: _licenseUri, + // projectUri: _projectUri, + // iconUri: _iconUri, + // requiredModules: RequiredModules, + // externalModuleDependencies: ExternalModuleDependencies, + // requiredScripts: RequiredScripts, + // externalScriptDependencies: ExternalScriptDependencies, + // releaseNotes: ReleaseNotes, + // privateData: PrivateData, + // description: Description, + // cmdletPassedIn: this); + + // if (!currentScriptInfo.TryCreateScriptFileInfoString( + // pSScriptFileString: out string psScriptFileContents, + // errors: out ErrorRecord[] errors)) + // { + // foreach (ErrorRecord err in errors) + // { + // WriteError(err); + // } + + // return; + // // TODO: Anam, currently only one error and you return. So maybe this shouldn't be a list? + // // But for extensability makes sense. + // } + } + + #endregion + } +} diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs new file mode 100644 index 000000000..5f1f2129c --- /dev/null +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -0,0 +1,277 @@ +using System.Net; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.PowerShell.PowerShellGet.UtilClasses; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using Microsoft.PowerShell.Commands; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// It retrieves a resource that was installed with Install-PSResource + /// Returns a single resource or multiple resource. + /// + [Cmdlet(VerbsData.Update, "PSScriptFileInfo")] + public sealed class UpdatePSScriptFileInfo : PSCmdlet + { + #region Members + private Uri _projectUri; + private Uri _licenseUri; + private Uri _iconUri; + + #endregion + + #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 information for the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Copyright { get; set; } + + /// + /// The description of the script + /// + [Parameter(Mandatory = true)] + [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; } + + /// + /// If used with Path parameter and .ps1 file specified at the path exists, it rewrites the file + /// + [Parameter] + public SwitchParameter Force { get; set; } + + /// + /// The GUID for the script + /// + [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; } + + /// + /// If specified, passes the contents of the created .ps1 file to the console + /// If -Path is not specified, then .ps1 contents will just be written out for the user + /// + [Parameter] + public SwitchParameter PassThru { get; set; } + + /// + /// The path the .ps1 script info file will be created at + /// + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty] + public string Path { 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; } + + /// + /// The list of modules required by the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public Hashtable[] RequiredModules { get; set; } + + /// + /// The list of scripts required by the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] RequiredScripts { get; set; } + + /// + /// The tags associated with the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string[] Tags { get; set; } + + /// + /// The version of the script + /// + [Parameter] + [ValidateNotNullOrEmpty()] + public string Version { get; set; } + + #endregion + + #region Methods + + protected override void ProcessRecord() + { + // validate Uri related parameters passed in as strings + if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUrl(uriString: ProjectUri, + cmdletPassedIn: this, + uriResult: out _projectUri, + errorRecord: out ErrorRecord projectErrorRecord)) + { + ThrowTerminatingError(projectErrorRecord); + } + + if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUrl(uriString: LicenseUri, + cmdletPassedIn: this, + uriResult: out _licenseUri, + errorRecord: out ErrorRecord licenseErrorRecord)) + { + ThrowTerminatingError(licenseErrorRecord); + } + + if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUrl(uriString: IconUri, + cmdletPassedIn: this, + uriResult: out _iconUri, + errorRecord: out ErrorRecord iconErrorRecord)) + { + ThrowTerminatingError(iconErrorRecord); + } + + bool usePath = false; + if (!String.IsNullOrEmpty(Path)) + { + if (!Path.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); + } + else if (File.Exists(Path) && !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); + } + + // if neither of those cases, Path is non-null and valid. + usePath = true; + } + else if (PassThru) + { + usePath = false; + } + else + { + // Either valid Path or PassThru parameter must be supplied. + var exMessage = "Either -Path parameter or -PassThru parameter value must be supplied to output script file contents to."; + var ex = new ArgumentException(exMessage); + var PathOrPassThruParameterRequiredError = new ErrorRecord(ex, "PathOrPassThruParameterRequired", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(PathOrPassThruParameterRequiredError); + } + + PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( + version: Version, + guid: Guid, + author: Author, + companyName: CompanyName, + copyright: Copyright, + tags: Tags, + licenseUri: _licenseUri, + projectUri: _projectUri, + iconUri: _iconUri, + requiredModules: RequiredModules, + externalModuleDependencies: ExternalModuleDependencies, + requiredScripts: RequiredScripts, + externalScriptDependencies: ExternalScriptDependencies, + releaseNotes: ReleaseNotes, + privateData: PrivateData, + description: Description, + cmdletPassedIn: this); + + if (!currentScriptInfo.TryCreateScriptFileInfoString( + pSScriptFileString: out string psScriptFileContents, + errors: out ErrorRecord[] errors)) + { + foreach (ErrorRecord err in errors) + { + WriteError(err); + } + + return; + // TODO: Anam, currently only one error and you return. So maybe this shouldn't be a list? + // But for extensability makes sense. + } + + if (usePath) + { + File.WriteAllText(Path, psScriptFileContents); // TODO: Anam better way to do this? + } + + if (!usePath || PassThru) + { + // TODO: Anam do we also write to console if Path AND PassThru used together? + WriteObject(psScriptFileContents); + } + } + + #endregion + } +} diff --git a/src/code/Utils.cs b/src/code/Utils.cs index ffd210eb1..7e479c3ad 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -114,6 +114,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 new string[]{}; + } + + return stringToConvertToArray.Split(delimeter, StringSplitOptions.RemoveEmptyEntries); + } + public static string[] ProcessNameWildcards( string[] pkgNames, out string[] errorMsgs, From 262cc81b6bf449f770484087741784baa1504406 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 9 Feb 2022 01:39:13 -0500 Subject: [PATCH 17/86] TestScriptFileInfo works and Utils creates ModuleSpecification objects --- src/code/NewPSScriptFileInfo.cs | 21 ++- src/code/PSScriptFileInfo.cs | 229 ++++++++++++++--------------- src/code/TestPSScriptFileInfo.cs | 2 +- src/code/UpdatePSScriptFileInfo.cs | 154 +++++++++---------- src/code/Utils.cs | 90 ++++++++++++ 5 files changed, 296 insertions(+), 200 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 21cdd7511..c6fac8bdd 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -24,6 +25,7 @@ public sealed class NewPSScriptFileInfo : PSCmdlet private Uri _projectUri; private Uri _licenseUri; private Uri _iconUri; + private List validatedRequiredModuleSpecifications; #endregion @@ -192,6 +194,22 @@ protected override void ProcessRecord() ThrowTerminatingError(iconErrorRecord); } + if (RequiredModules.Length > 0) + { + // TODO: ANAM have this return array not list for mod specs + Utils.CreateModuleSpecification( + moduleSpecHashtables: RequiredModules, + out validatedRequiredModuleSpecifications, + out ErrorRecord[] moduleSpecErrors); + if (moduleSpecErrors.Length > 0) + { + foreach (ErrorRecord err in moduleSpecErrors) + { + WriteError(err); + } + } + } + bool usePath = false; if (!String.IsNullOrEmpty(Path)) { @@ -237,7 +255,8 @@ protected override void ProcessRecord() licenseUri: _licenseUri, projectUri: _projectUri, iconUri: _iconUri, - requiredModules: RequiredModules, + requiredModules: validatedRequiredModuleSpecifications.ToArray(), + // requiredModules: RequiredModules, externalModuleDependencies: ExternalModuleDependencies, requiredScripts: RequiredScripts, externalScriptDependencies: ExternalScriptDependencies, diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 40e9512f7..2af8aa723 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -1,4 +1,3 @@ -using System.Reflection; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -68,14 +67,6 @@ public sealed class PSScriptFileInfo /// public Uri IconUri { get; } - // /// - // /// The list of modules required by the script - // /// TODO: in V2 this had type Object[] - // /// - // [Parameter] - // [ValidateNotNullOrEmpty()] - // public string[] RequiredModules { get; set; } - /// /// The list of modules required by the script /// Hashtable keys: GUID, MaxVersion, Name (Required), RequiredVersion, Version @@ -197,8 +188,8 @@ public PSScriptFileInfo( Uri licenseUri, Uri projectUri, Uri iconUri, - // ModuleSpecification[] requiredModules, // TODO: in V2 this was Object[] - Hashtable[] requiredModules, + ModuleSpecification[] requiredModules, // TODO: in V2 this was Object[] + // Hashtable[] requiredModules, string[] externalModuleDependencies, string[] requiredScripts, string[] externalScriptDependencies, @@ -213,15 +204,15 @@ PSCmdlet cmdletPassedIn author = Environment.UserName; } - List validatedModuleSpecs = new List(); - if (requiredModules.Length > 0) - { - CreateModuleSpecification(requiredModules, out validatedModuleSpecs, out ErrorRecord[] errors); - foreach (ErrorRecord err in errors) - { - cmdletPassedIn.WriteError(err); - } - } + // List validatedModuleSpecs = new List(); + // if (requiredModules.Length > 0) + // { + // CreateModuleSpecification(requiredModules, out validatedModuleSpecs, out ErrorRecord[] errors); + // foreach (ErrorRecord err in errors) + // { + // cmdletPassedIn.WriteError(err); + // } + // } Version = !String.IsNullOrEmpty(version) ? new Version (version) : new Version("1.0.0.0"); Guid = (guid == null || guid == Guid.Empty) ? Guid.NewGuid() : guid; @@ -232,7 +223,7 @@ PSCmdlet cmdletPassedIn LicenseUri = licenseUri; ProjectUri = projectUri; IconUri = iconUri; - RequiredModules = validatedModuleSpecs.ToArray() ?? new ModuleSpecification[]{}; + RequiredModules = requiredModules ?? new ModuleSpecification[]{}; ExternalModuleDependencies = externalModuleDependencies ?? Utils.EmptyStrArray; RequiredScripts = requiredScripts ?? Utils.EmptyStrArray; ExternalScriptDependencies = externalScriptDependencies ?? Utils.EmptyStrArray; @@ -273,13 +264,13 @@ Feature 5 public static bool TryParseScriptFileInfo( string scriptFileInfo, out PSScriptFileInfo parsedScript, - out Hashtable parsedPSScriptInfoHashtable, - out ErrorRecord[] errors2, + out Hashtable parsedPSScriptInfoHashtable, // TODO: Anam, remove? + out ErrorRecord[] moduleSpecErrors, PSCmdlet cmdletPassedIn) { parsedScript = null; parsedPSScriptInfoHashtable = new Hashtable(); - errors2 = new ErrorRecord[]{}; + moduleSpecErrors = new ErrorRecord[]{}; List errorsList = new List(); bool successfullyParsed = false; @@ -322,7 +313,7 @@ public static bool TryParseScriptFileInfo( var psCommentMissingError = new ErrorRecord(ex, "psScriptInfoCommentMissingError", ErrorCategory.ParserError, null); errorsList.Add(psCommentMissingError); - errors2 = errorsList.ToArray(); + moduleSpecErrors = errorsList.ToArray(); return successfullyParsed; } @@ -382,7 +373,7 @@ public static bool TryParseScriptFileInfo( // get RequiredModules ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; - ReadOnlyCollection parsedModules = null; + ReadOnlyCollection parsedModules = new List().AsReadOnly(); if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) { @@ -443,9 +434,6 @@ public static bool TryParseScriptFileInfo( Uri parsedProjectUri = new Uri((string) parsedPSScriptInfoHashtable["PROJECTURI"]); Uri parsedIconUri = new Uri((string) parsedPSScriptInfoHashtable["ICONURI"]); - - - // TODO: Anam Hashtable should contain all keys, but values may be String.empty parsedScript = new PSScriptFileInfo( version: parsedVersion, @@ -457,8 +445,7 @@ public static bool TryParseScriptFileInfo( licenseUri: parsedLicenseUri, projectUri: parsedProjectUri, iconUri: parsedIconUri, - // ModuleSpecification[] requiredModules, // TODO: in V2 this was Object[] - requiredModules: new Hashtable[]{}, + requiredModules: parsedModules.ToArray(), externalModuleDependencies: parsedExternalModuleDependencies, requiredScripts: parsedRequiredScripts, externalScriptDependencies: parsedExternalScriptDependencies, @@ -683,96 +670,96 @@ out ErrorRecord error return psHelpInfoSuccessfullyCreated; } - public static void CreateModuleSpecification( - Hashtable[] moduleSpecHashtables, - out List validatedModuleSpecs, - out ErrorRecord[] errors - ) - { - // bool successfullyCreatedModuleSpecs = false; - List errorList = new List(); - List moduleSpecsList = new List(); - - foreach(Hashtable moduleSpec in moduleSpecHashtables) - { - if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) - { - var exMessage = "RequiredModules Hashtable entry 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); - continue; - } - - // at this point it must contain ModuleName key. - string moduleSpecName = (string) moduleSpec["ModuleName"]; - ModuleSpecification currentModuleSpec = null; - if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) - { - // pass to ModuleSpecification(string) constructor - currentModuleSpec = new ModuleSpecification(moduleSpecName); - if (currentModuleSpec != null) - { - moduleSpecsList.Add(currentModuleSpec); - } - else - { - var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); - var ex = new ArgumentException(exMessage); - var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); - errorList.Add(ModuleSpecNotCreatedError); - } - } - else - { - // TODO: ANAM perhaps not else - string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; - string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; - string moduleSpecRequiredVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; - Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; // TODO: ANAM this can be the default - - 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); - } - - currentModuleSpec = new ModuleSpecification(moduleSpecHash); - if (currentModuleSpec != null) - { - moduleSpecsList.Add(currentModuleSpec); - } - else - { - var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); - var ex = new ArgumentException(exMessage); - var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); - errorList.Add(ModuleSpecNotCreatedError); - } - } - } - - errors = errorList.ToArray(); - validatedModuleSpecs = moduleSpecsList; - // return successfullyCreatedModuleSpecs; - } + // public static void CreateModuleSpecification( + // Hashtable[] moduleSpecHashtables, + // out List validatedModuleSpecs, + // out ErrorRecord[] errors + // ) + // { + // // bool successfullyCreatedModuleSpecs = false; + // List errorList = new List(); + // List moduleSpecsList = new List(); + + // foreach(Hashtable moduleSpec in moduleSpecHashtables) + // { + // if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) + // { + // var exMessage = "RequiredModules Hashtable entry 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); + // continue; + // } + + // // at this point it must contain ModuleName key. + // string moduleSpecName = (string) moduleSpec["ModuleName"]; + // ModuleSpecification currentModuleSpec = null; + // if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) + // { + // // pass to ModuleSpecification(string) constructor + // currentModuleSpec = new ModuleSpecification(moduleSpecName); + // if (currentModuleSpec != null) + // { + // moduleSpecsList.Add(currentModuleSpec); + // } + // else + // { + // var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + // var ex = new ArgumentException(exMessage); + // var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + // errorList.Add(ModuleSpecNotCreatedError); + // } + // } + // else + // { + // // TODO: ANAM perhaps not else + // string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; + // string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; + // string moduleSpecRequiredVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; + // Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; // TODO: ANAM this can be the default + + // 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); + // } + + // currentModuleSpec = new ModuleSpecification(moduleSpecHash); + // if (currentModuleSpec != null) + // { + // moduleSpecsList.Add(currentModuleSpec); + // } + // else + // { + // var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + // var ex = new ArgumentException(exMessage); + // var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + // errorList.Add(ModuleSpecNotCreatedError); + // } + // } + // } + + // errors = errorList.ToArray(); + // validatedModuleSpecs = moduleSpecsList; + // // return successfullyCreatedModuleSpecs; + // } #endregion diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 8a06deac3..ea5fa0835 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -66,7 +66,7 @@ protected override void ProcessRecord() bool isValidPSScriptFile = PSScriptFileInfo.TryParseScriptFileInfo( scriptFileInfo: resolvedPath, parsedScript: out PSScriptFileInfo parsedScriptInfo, - errors2: out ErrorRecord[] errors, + moduleSpecErrors: out ErrorRecord[] errors, parsedPSScriptInfoHashtable: out Hashtable parsedHash, cmdletPassedIn: this); diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 5f1f2129c..390fe2781 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -192,84 +192,84 @@ protected override void ProcessRecord() ThrowTerminatingError(iconErrorRecord); } - bool usePath = false; - if (!String.IsNullOrEmpty(Path)) - { - if (!Path.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); - } - else if (File.Exists(Path) && !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); - } - - // if neither of those cases, Path is non-null and valid. - usePath = true; - } - else if (PassThru) - { - usePath = false; - } - else - { - // Either valid Path or PassThru parameter must be supplied. - var exMessage = "Either -Path parameter or -PassThru parameter value must be supplied to output script file contents to."; - var ex = new ArgumentException(exMessage); - var PathOrPassThruParameterRequiredError = new ErrorRecord(ex, "PathOrPassThruParameterRequired", ErrorCategory.InvalidArgument, null); - ThrowTerminatingError(PathOrPassThruParameterRequiredError); - } - - PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( - version: Version, - guid: Guid, - author: Author, - companyName: CompanyName, - copyright: Copyright, - tags: Tags, - licenseUri: _licenseUri, - projectUri: _projectUri, - iconUri: _iconUri, - requiredModules: RequiredModules, - externalModuleDependencies: ExternalModuleDependencies, - requiredScripts: RequiredScripts, - externalScriptDependencies: ExternalScriptDependencies, - releaseNotes: ReleaseNotes, - privateData: PrivateData, - description: Description, - cmdletPassedIn: this); - - if (!currentScriptInfo.TryCreateScriptFileInfoString( - pSScriptFileString: out string psScriptFileContents, - errors: out ErrorRecord[] errors)) - { - foreach (ErrorRecord err in errors) - { - WriteError(err); - } - - return; - // TODO: Anam, currently only one error and you return. So maybe this shouldn't be a list? - // But for extensability makes sense. - } - - if (usePath) - { - File.WriteAllText(Path, psScriptFileContents); // TODO: Anam better way to do this? - } + // bool usePath = false; + // if (!String.IsNullOrEmpty(Path)) + // { + // if (!Path.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); + // } + // else if (File.Exists(Path) && !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); + // } + + // // if neither of those cases, Path is non-null and valid. + // usePath = true; + // } + // else if (PassThru) + // { + // usePath = false; + // } + // else + // { + // // Either valid Path or PassThru parameter must be supplied. + // var exMessage = "Either -Path parameter or -PassThru parameter value must be supplied to output script file contents to."; + // var ex = new ArgumentException(exMessage); + // var PathOrPassThruParameterRequiredError = new ErrorRecord(ex, "PathOrPassThruParameterRequired", ErrorCategory.InvalidArgument, null); + // ThrowTerminatingError(PathOrPassThruParameterRequiredError); + // } + + // PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( + // version: Version, + // guid: Guid, + // author: Author, + // companyName: CompanyName, + // copyright: Copyright, + // tags: Tags, + // licenseUri: _licenseUri, + // projectUri: _projectUri, + // iconUri: _iconUri, + // requiredModules: RequiredModules, + // externalModuleDependencies: ExternalModuleDependencies, + // requiredScripts: RequiredScripts, + // externalScriptDependencies: ExternalScriptDependencies, + // releaseNotes: ReleaseNotes, + // privateData: PrivateData, + // description: Description, + // cmdletPassedIn: this); + + // if (!currentScriptInfo.TryCreateScriptFileInfoString( + // pSScriptFileString: out string psScriptFileContents, + // errors: out ErrorRecord[] errors)) + // { + // foreach (ErrorRecord err in errors) + // { + // WriteError(err); + // } + + // return; + // // TODO: Anam, currently only one error and you return. So maybe this shouldn't be a list? + // // But for extensability makes sense. + // } + + // if (usePath) + // { + // File.WriteAllText(Path, psScriptFileContents); // TODO: Anam better way to do this? + // } - if (!usePath || PassThru) - { - // TODO: Anam do we also write to console if Path AND PassThru used together? - WriteObject(psScriptFileContents); - } + // if (!usePath || PassThru) + // { + // // TODO: Anam do we also write to console if Path AND PassThru used together? + // WriteObject(psScriptFileContents); + // } } #endregion diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 7e479c3ad..9b92a018d 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -984,6 +984,96 @@ public static Hashtable ConvertJsonToHashtable( return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; } + public static void CreateModuleSpecification( + Hashtable[] moduleSpecHashtables, + out List validatedModuleSpecs, + out ErrorRecord[] errors) + { + // bool successfullyCreatedModuleSpecs = false; + List errorList = new List(); + List moduleSpecsList = new List(); + + foreach(Hashtable moduleSpec in moduleSpecHashtables) + { + if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) + { + var exMessage = "RequiredModules Hashtable entry 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); + continue; + } + + // at this point it must contain ModuleName key. + string moduleSpecName = (string) moduleSpec["ModuleName"]; + ModuleSpecification currentModuleSpec = null; + if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) + { + // pass to ModuleSpecification(string) constructor + currentModuleSpec = new ModuleSpecification(moduleSpecName); + if (currentModuleSpec != null) + { + moduleSpecsList.Add(currentModuleSpec); + } + else + { + var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + var ex = new ArgumentException(exMessage); + var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + errorList.Add(ModuleSpecNotCreatedError); + } + } + else + { + // TODO: ANAM perhaps not else + string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; + string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; + string moduleSpecRequiredVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; + Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; // TODO: ANAM this can be the default + + 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); + } + + currentModuleSpec = new ModuleSpecification(moduleSpecHash); + if (currentModuleSpec != null) + { + moduleSpecsList.Add(currentModuleSpec); + } + else + { + var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + var ex = new ArgumentException(exMessage); + var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); + errorList.Add(ModuleSpecNotCreatedError); + } + } + } + + errors = errorList.ToArray(); + validatedModuleSpecs = moduleSpecsList; + // return successfullyCreatedModuleSpecs; + } + #endregion #region Directory and File From 3f4ed6fd1a67d119fd47bd3cfba7a2b483fe6cf1 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 9 Feb 2022 16:56:03 -0500 Subject: [PATCH 18/86] Test-ScriptFileInfo now works --- src/code/NewPSScriptFileInfo.cs | 3 +- src/code/PSScriptFileInfo.cs | 164 +++++++------------------------ src/code/TestPSScriptFileInfo.cs | 59 +++-------- 3 files changed, 51 insertions(+), 175 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index c6fac8bdd..af059e2e2 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -262,8 +262,7 @@ protected override void ProcessRecord() externalScriptDependencies: ExternalScriptDependencies, releaseNotes: ReleaseNotes, privateData: PrivateData, - description: Description, - cmdletPassedIn: this); + description: Description); if (!currentScriptInfo.TryCreateScriptFileInfoString( pSScriptFileString: out string psScriptFileContents, diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 2af8aa723..62566bc7b 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -195,8 +195,7 @@ public PSScriptFileInfo( string[] externalScriptDependencies, string[] releaseNotes, string privateData, - string description, - PSCmdlet cmdletPassedIn + string description ) { if (String.IsNullOrEmpty(author)) @@ -204,16 +203,6 @@ PSCmdlet cmdletPassedIn author = Environment.UserName; } - // List validatedModuleSpecs = new List(); - // if (requiredModules.Length > 0) - // { - // CreateModuleSpecification(requiredModules, out validatedModuleSpecs, out ErrorRecord[] errors); - // foreach (ErrorRecord err in errors) - // { - // cmdletPassedIn.WriteError(err); - // } - // } - Version = !String.IsNullOrEmpty(version) ? new Version (version) : new Version("1.0.0.0"); Guid = (guid == null || guid == Guid.Empty) ? Guid.NewGuid() : guid; Author = !String.IsNullOrEmpty(author) ? author : Environment.UserName; @@ -264,12 +253,9 @@ Feature 5 public static bool TryParseScriptFileInfo( string scriptFileInfo, out PSScriptFileInfo parsedScript, - out Hashtable parsedPSScriptInfoHashtable, // TODO: Anam, remove? - out ErrorRecord[] moduleSpecErrors, - PSCmdlet cmdletPassedIn) + out ErrorRecord[] moduleSpecErrors) { parsedScript = null; - parsedPSScriptInfoHashtable = new Hashtable(); moduleSpecErrors = new ErrorRecord[]{}; List errorsList = new List(); bool successfullyParsed = false; @@ -297,10 +283,9 @@ public static bool TryParseScriptFileInfo( return successfullyParsed; } else if (ast != null) - { - // TODO: Anam do we still need to check if ast is not null? - + { // Get the block/group comment beginning with <#PSScriptInfo + Hashtable parsedPSScriptInfoHashtable = new Hashtable(); List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); string commentPattern = "<#PSScriptInfo"; Regex rg = new Regex(commentPattern); @@ -367,7 +352,8 @@ public static bool TryParseScriptFileInfo( var ex = new ArgumentException(message); var psScriptMissingDescriptionPropertyError = new ErrorRecord(ex, "psScriptDescriptionMissingDescription", ErrorCategory.ParserError, null); errorsList.Add(psScriptMissingDescriptionPropertyError); - return false; + successfullyParsed = false; + return successfullyParsed; } } @@ -378,10 +364,6 @@ public static bool TryParseScriptFileInfo( if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) { ReadOnlyCollection parsedRequiredModules = parsedScriptRequirements.RequiredModules; - parsedPSScriptInfoHashtable.Add("RequiredModules", parsedRequiredModules); - Console.WriteLine(parsedRequiredModules.ToString()); - Console.WriteLine("and as a value:"); - Console.WriteLine((ReadOnlyCollection) parsedPSScriptInfoHashtable["RequiredModules"]); if (parsedPSScriptInfoHashtable.ContainsKey("RequiredModules")) { parsedModules = (ReadOnlyCollection) parsedPSScriptInfoHashtable["RequiredModules"]; @@ -414,12 +396,14 @@ public static bool TryParseScriptFileInfo( string parsedVersion = (string) parsedPSScriptInfoHashtable["VERSION"]; string parsedAuthor = (string) parsedPSScriptInfoHashtable["AUTHOR"]; Guid parsedGuid = String.IsNullOrEmpty((string)parsedPSScriptInfoHashtable["GUID"]) ? Guid.NewGuid() : new Guid((string) parsedPSScriptInfoHashtable["GUID"]); - if (!String.IsNullOrEmpty(parsedVersion) || !String.IsNullOrEmpty(parsedAuthor) || parsedGuid == Guid.Empty) + if (String.IsNullOrEmpty(parsedVersion) || String.IsNullOrEmpty(parsedAuthor) || parsedGuid == Guid.Empty) { var message = String.Format("PSScript file is missing one of the following required properties: Version, Author, Guid"); var ex = new ArgumentException(message); var psScriptMissingRequiredPropertyError = new ErrorRecord(ex, "psScriptMissingRequiredProperty", ErrorCategory.ParserError, null); errorsList.Add(psScriptMissingRequiredPropertyError); + successfullyParsed = false; + return successfullyParsed; } try @@ -430,11 +414,26 @@ public static bool TryParseScriptFileInfo( string[] parsedRequiredScripts = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["REQUIREDSCRIPTS"]); string[] parsedExternalScriptDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["EXTERNALSCRIPTDEPENDENCIES"]); string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["RELEASENOTES"]); - Uri parsedLicenseUri = new Uri((string) parsedPSScriptInfoHashtable["LICENSEURI"]); - Uri parsedProjectUri = new Uri((string) parsedPSScriptInfoHashtable["PROJECTURI"]); - Uri parsedIconUri = new Uri((string) parsedPSScriptInfoHashtable["ICONURI"]); + Uri parsedLicenseUri = null; + Uri parsedProjectUri = null; + Uri parsedIconUri = null; + + if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["LICENSEURI"])) + { + Uri.TryCreate((string) parsedPSScriptInfoHashtable["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri); + } + + if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["PROJECTURI"])) + { + Uri.TryCreate((string) parsedPSScriptInfoHashtable["PROJECTURI"], UriKind.Absolute, out parsedProjectUri); + } + + if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["ICONURI"])) + { + Uri.TryCreate((string) parsedPSScriptInfoHashtable["ICONURI"], UriKind.Absolute, out parsedProjectUri); + } - // TODO: Anam Hashtable should contain all keys, but values may be String.empty + // parsedPSScriptInfoHashtable should contain all keys, but values may be empty (i.e empty array, String.empty) parsedScript = new PSScriptFileInfo( version: parsedVersion, guid: parsedGuid, @@ -451,8 +450,7 @@ public static bool TryParseScriptFileInfo( externalScriptDependencies: parsedExternalScriptDependencies, releaseNotes: parsedReleaseNotes, privateData: (string) parsedPSScriptInfoHashtable["PRIVATEDATA"], - description: scriptCommentInfo.Description, - cmdletPassedIn: cmdletPassedIn); + description: scriptCommentInfo.Description); } catch (Exception e) { @@ -460,6 +458,7 @@ public static bool TryParseScriptFileInfo( var ex = new ArgumentException(message); var PSScriptFileInfoObjectNotCreatedFromFileError = new ErrorRecord(ex, "PSScriptFileInfoObjectNotCreatedFromFile", ErrorCategory.ParserError, null); errorsList.Add(PSScriptFileInfoObjectNotCreatedFromFileError); + successfullyParsed = false; } } else @@ -618,26 +617,26 @@ out ErrorRecord error psHelpInfoSuccessfullyCreated = true; psHelpInfoLines.Add("<#\n"); - psHelpInfoLines.Add(String.Format(".DESCRIPTION {0}", Description)); + psHelpInfoLines.Add(String.Format(".DESCRIPTION\n{0}", Description)); if (!String.IsNullOrEmpty(Synopsis)) { - psHelpInfoLines.Add(String.Format(".SYNOPSIS {0}", Synopsis)); + psHelpInfoLines.Add(String.Format(".SYNOPSIS\n{0}", Synopsis)); } foreach (string currentExample in Example) { - psHelpInfoLines.Add(String.Format(".EXAMPLE {0}", currentExample)); + psHelpInfoLines.Add(String.Format(".EXAMPLE\n{0}", currentExample)); } foreach (string input in Inputs) { - psHelpInfoLines.Add(String.Format(".INPUTS {0}", input)); + psHelpInfoLines.Add(String.Format(".INPUTS\n{0}", input)); } foreach (string output in Outputs) { - psHelpInfoLines.Add(String.Format(".OUTPUTS {0}", output)); + psHelpInfoLines.Add(String.Format(".OUTPUTS\n{0}", output)); } if (Notes.Length > 0) @@ -647,7 +646,7 @@ out ErrorRecord error foreach (string link in Links) { - psHelpInfoLines.Add(String.Format(".LINK {0}", link)); + psHelpInfoLines.Add(String.Format(".LINK\n{0}", link)); } if (Component.Length > 0) @@ -670,97 +669,6 @@ out ErrorRecord error return psHelpInfoSuccessfullyCreated; } - // public static void CreateModuleSpecification( - // Hashtable[] moduleSpecHashtables, - // out List validatedModuleSpecs, - // out ErrorRecord[] errors - // ) - // { - // // bool successfullyCreatedModuleSpecs = false; - // List errorList = new List(); - // List moduleSpecsList = new List(); - - // foreach(Hashtable moduleSpec in moduleSpecHashtables) - // { - // if (!moduleSpec.ContainsKey("ModuleName") || String.IsNullOrEmpty((string) moduleSpec["ModuleName"])) - // { - // var exMessage = "RequiredModules Hashtable entry 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); - // continue; - // } - - // // at this point it must contain ModuleName key. - // string moduleSpecName = (string) moduleSpec["ModuleName"]; - // ModuleSpecification currentModuleSpec = null; - // if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) - // { - // // pass to ModuleSpecification(string) constructor - // currentModuleSpec = new ModuleSpecification(moduleSpecName); - // if (currentModuleSpec != null) - // { - // moduleSpecsList.Add(currentModuleSpec); - // } - // else - // { - // var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); - // var ex = new ArgumentException(exMessage); - // var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); - // errorList.Add(ModuleSpecNotCreatedError); - // } - // } - // else - // { - // // TODO: ANAM perhaps not else - // string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; - // string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; - // string moduleSpecRequiredVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; - // Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; // TODO: ANAM this can be the default - - // 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); - // } - - // currentModuleSpec = new ModuleSpecification(moduleSpecHash); - // if (currentModuleSpec != null) - // { - // moduleSpecsList.Add(currentModuleSpec); - // } - // else - // { - // var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); - // var ex = new ArgumentException(exMessage); - // var ModuleSpecNotCreatedError = new ErrorRecord(ex, "ModuleSpecificationNotCreated", ErrorCategory.InvalidArgument, null); - // errorList.Add(ModuleSpecNotCreatedError); - // } - // } - // } - - // errors = errorList.ToArray(); - // validatedModuleSpecs = moduleSpecsList; - // // return successfullyCreatedModuleSpecs; - // } - #endregion } diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index ea5fa0835..0e2e66a75 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -8,8 +8,10 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Management.Automation; using Microsoft.PowerShell.Commands; +using System.Collections.ObjectModel; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { @@ -63,53 +65,20 @@ protected override void ProcessRecord() var resolvedPath = resolvedPaths[0].Path; - bool isValidPSScriptFile = PSScriptFileInfo.TryParseScriptFileInfo( + if (!PSScriptFileInfo.TryParseScriptFileInfo( scriptFileInfo: resolvedPath, parsedScript: out PSScriptFileInfo parsedScriptInfo, - moduleSpecErrors: out ErrorRecord[] errors, - parsedPSScriptInfoHashtable: out Hashtable parsedHash, - cmdletPassedIn: this); - - WriteVerbose("is valid: " + isValidPSScriptFile); - - - - - - - - // PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( - // version: Version, - // guid: Guid, - // author: Author, - // companyName: CompanyName, - // copyright: Copyright, - // tags: Tags, - // licenseUri: _licenseUri, - // projectUri: _projectUri, - // iconUri: _iconUri, - // requiredModules: RequiredModules, - // externalModuleDependencies: ExternalModuleDependencies, - // requiredScripts: RequiredScripts, - // externalScriptDependencies: ExternalScriptDependencies, - // releaseNotes: ReleaseNotes, - // privateData: PrivateData, - // description: Description, - // cmdletPassedIn: this); - - // if (!currentScriptInfo.TryCreateScriptFileInfoString( - // pSScriptFileString: out string psScriptFileContents, - // errors: out ErrorRecord[] errors)) - // { - // foreach (ErrorRecord err in errors) - // { - // WriteError(err); - // } - - // return; - // // TODO: Anam, currently only one error and you return. So maybe this shouldn't be a list? - // // But for extensability makes sense. - // } + moduleSpecErrors: out ErrorRecord[] errors)) + { + foreach (ErrorRecord error in errors) + { + WriteError(error); + } + } + else + { + WriteObject(parsedScriptInfo); + } } #endregion From 04ace4b5403c4df91cea6690b8dd9f7fb746013a Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 10 Feb 2022 16:42:08 -0500 Subject: [PATCH 19/86] add code for Update-ScriptFileInfo; still need to test --- src/code/PSScriptFileInfo.cs | 197 +++++++++++++++++++++++++---- src/code/TestPSScriptFileInfo.cs | 3 +- src/code/UpdatePSScriptFileInfo.cs | 191 ++++++++++++++++------------ 3 files changed, 286 insertions(+), 105 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 62566bc7b..c17f04849 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -24,48 +24,48 @@ public sealed class PSScriptFileInfo /// /// the Version of the script /// - public Version Version { get; } + public Version Version { get; set; } /// /// the GUID for the script /// - public Guid Guid { get; } + public Guid Guid { get; set; } /// /// the author for the script /// - public string Author { get; } + public string Author { get; set; } /// /// the name of the company owning the script /// [ValidateRange(0, 50)] - public string CompanyName { get; } + public string CompanyName { get; set; } /// /// the copyright information for the script /// - public string Copyright { get; } + public string Copyright { get; set; } /// /// the tags for the script /// - public string[] Tags { get; } + public string[] Tags { get; set; } /// /// the Uri for the license of the script /// - public Uri LicenseUri { get; } + public Uri LicenseUri { get; set; } /// /// the Uri for the project relating to the script /// - public Uri ProjectUri { get; } + public Uri ProjectUri { get; set; } /// /// the Uri for the icon relating to the script /// - public Uri IconUri { get; } + public Uri IconUri { get; set; } /// /// The list of modules required by the script @@ -78,22 +78,22 @@ public sealed class PSScriptFileInfo /// /// the list of external module dependencies for the script /// - public string[] ExternalModuleDependencies { get; } = new string[]{}; + public string[] ExternalModuleDependencies { get; set; } = new string[]{}; /// /// the list of required scripts for the parent script /// - public string[] RequiredScripts { get; } = new string[]{}; + public string[] RequiredScripts { get; set; } = new string[]{}; /// /// the list of external script dependencies for the script /// - public string[] ExternalScriptDependencies { get; } = new string[]{}; + public string[] ExternalScriptDependencies { get; set; } = new string[]{}; /// /// the release notes relating to the script /// - public string[] ReleaseNotes { get; } = new string[]{}; + public string[] ReleaseNotes { get; set; } = new string[]{}; /// /// The private data associated with the script @@ -251,30 +251,30 @@ Feature 5 #region Public Static Methods public static bool TryParseScriptFileInfo( - string scriptFileInfo, + string scriptFileInfoPath, out PSScriptFileInfo parsedScript, - out ErrorRecord[] moduleSpecErrors) + out ErrorRecord[] errors) { parsedScript = null; - moduleSpecErrors = new ErrorRecord[]{}; + errors = new ErrorRecord[]{}; List errorsList = new List(); bool successfullyParsed = false; - if (scriptFileInfo.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + if (scriptFileInfoPath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { // Parse the script file var ast = Parser.ParseFile( - scriptFileInfo, + scriptFileInfoPath, out Token[] tokens, out ParseError[] parserErrors); if (parserErrors.Length > 0 && !String.Equals(parserErrors[0].ErrorId, "WorkflowNotSupportedInPowerShellCore", StringComparison.OrdinalIgnoreCase)) { - // TODO: Anam - do we want to completely ignore WorkFlowNotSupportedInPowerShellCore error (not even write) + // TODO: do we want to completely ignore WorkFlowNotSupportedInPowerShellCore error (not even write) // or do we want to write, but not return if it's just that error? foreach (ParseError err in parserErrors) { - var message = String.Format("Could not parse '{0}' as a PowerShell script file due to {1}.", scriptFileInfo, err.Message); + var message = String.Format("Could not parse '{0}' as a PowerShell script file due to {1}.", scriptFileInfoPath, err.Message); var ex = new ArgumentException(message); var psScriptFileParseError = new ErrorRecord(ex, err.ErrorId, ErrorCategory.ParserError, null); errorsList.Add(psScriptFileParseError); @@ -298,7 +298,7 @@ public static bool TryParseScriptFileInfo( var psCommentMissingError = new ErrorRecord(ex, "psScriptInfoCommentMissingError", ErrorCategory.ParserError, null); errorsList.Add(psCommentMissingError); - moduleSpecErrors = errorsList.ToArray(); + errors = errorsList.ToArray(); return successfullyParsed; } @@ -316,7 +316,7 @@ public static bool TryParseScriptFileInfo( if (commentLines.Count() > 2) { - // TODO: Anam is it an error if the metadata property is empty? + // TODO: is it an error if the metadata property is empty? for (int i = 1; i < commentLines.Count(); i++) { string line = commentLines[i]; @@ -371,7 +371,7 @@ public static bool TryParseScriptFileInfo( } // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow - // TODO: Anam DefinedWorkflow is no longer supported as of PowerShellCore 6+, do we ignore error or reject script? + // TODO: DefinedWorkflow is no longer supported as of PowerShellCore 6+, do we ignore error or reject script? var parsedFunctionAst = ast.FindAll(a => a is FunctionDefinitionAst, true); List allCommands = new List(); if (allCommands.Count() > 0) @@ -384,7 +384,7 @@ public static bool TryParseScriptFileInfo( List allCommandNames = allCommands.Select(a => a.Name).Distinct().ToList(); parsedPSScriptInfoHashtable.Add("DefinedCommands", allCommandNames); - // b.Body.Extent.Text to get actual content. So "Function b.Name c.Body.Extent.Text" is that whole line lol + // b.Body.Extent.Text to get actual content. So "Function b.Name c.Body.Extent.Text" is that whole line List allFunctionNames = allCommands.Where(a => !a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); parsedPSScriptInfoHashtable.Add("DefinedFunctions", allFunctionNames); @@ -474,6 +474,152 @@ public static bool TryParseScriptFileInfo( return successfullyParsed; } + public static bool TryUpdateRequestedFields( + ref PSScriptFileInfo parsedScript, + out ErrorRecord[] errors, + out string updatedPSScriptFileContents, + 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) + { + bool successfullyUpdated = false; + updatedPSScriptFileContents = String.Empty; + errors = new ErrorRecord[]{}; + List errorsList = new List(); + + if (parsedScript == null) + { + var message = String.Format("PSScriptFileInfo object to update is null."); + var ex = new ArgumentException(message); + var nullPSScriptFileInfoObjectToUpdateError = new ErrorRecord(ex, "NullPSScriptFileInfoObjectToUpdate", ErrorCategory.ParserError, null); + errorsList.Add(nullPSScriptFileInfoObjectToUpdateError); + errors = errorsList.ToArray(); + return successfullyUpdated; + } + + PSScriptFileInfo tempScriptFileInfoObject = new PSScriptFileInfo(); + + // create new PSScriptFileInfo with updated fields + try + { + if (!String.IsNullOrEmpty(version)) + { + if (!System.Version.TryParse(version, out Version updatedVersion)) + { + tempScriptFileInfoObject.Version = new Version("2.0.0.0"); + } + else + { + tempScriptFileInfoObject.Version = updatedVersion; + } + } + + if (guid != Guid.Empty) + { + tempScriptFileInfoObject.Guid = guid; + } + + if (!String.IsNullOrEmpty(author)) + { + tempScriptFileInfoObject.Author = author; + } + + if (!String.IsNullOrEmpty(companyName)){ + tempScriptFileInfoObject.CompanyName = companyName; + } + + if (!String.IsNullOrEmpty(copyright)){ + tempScriptFileInfoObject.Copyright = copyright; + } + + if (tags != null && tags.Length != 0){ + tempScriptFileInfoObject.Tags = tags; + } + + if (licenseUri != null && !licenseUri.Equals(default(Uri))){ + tempScriptFileInfoObject.LicenseUri = licenseUri; + } + + if (projectUri != null && !projectUri.Equals(default(Uri))){ + tempScriptFileInfoObject.ProjectUri = projectUri; + } + + if (iconUri != null && !iconUri.Equals(default(Uri))){ + tempScriptFileInfoObject.IconUri = iconUri; + } + + if (requiredModules != null && requiredModules.Length != 0){ + tempScriptFileInfoObject.RequiredModules = requiredModules; + } + + if (externalModuleDependencies != null && externalModuleDependencies.Length != 0){ + tempScriptFileInfoObject.ExternalModuleDependencies = externalModuleDependencies; + } + + if (requiredScripts != null && requiredScripts.Length != 0) + { + tempScriptFileInfoObject.RequiredScripts = requiredScripts; + } + + if (externalScriptDependencies != null && externalScriptDependencies.Length != 0){ + tempScriptFileInfoObject.ExternalScriptDependencies = externalScriptDependencies; + } + + if (releaseNotes != null && releaseNotes.Length != 0) + { + tempScriptFileInfoObject.ReleaseNotes = releaseNotes; + } + + if (!String.IsNullOrEmpty(privateData)) + { + tempScriptFileInfoObject.PrivateData = privateData; + } + + if (!String.IsNullOrEmpty(description)) + { + tempScriptFileInfoObject.Description = description; + } + } + catch (Exception exception) + { + var message = String.Format(".ps1 file and associated PSScriptFileInfo object's field could not be updated due to {0}.", exception.Message); + var ex = new ArgumentException(message); + var PSScriptFileInfoFieldCouldNotBeUpdatedError = new ErrorRecord(ex, "PSScriptFileInfoFieldCouldNotBeUpdated", ErrorCategory.ParserError, null); + errorsList.Add(PSScriptFileInfoFieldCouldNotBeUpdatedError); + errors = errorsList.ToArray(); + return successfullyUpdated; + } + + + // create string contents for .ps1 file + if (tempScriptFileInfoObject.TryCreateScriptFileInfoString( + pSScriptFileString: out string psScriptFileContents, + errors: out ErrorRecord[] createFileContentErrors)) + { + errorsList.AddRange(createFileContentErrors); + errors = errorsList.ToArray(); + return successfullyUpdated; + } + + // TODO: must also add Ast.EndBlock.Extent.Text or last n lines from file. + successfullyUpdated = true; + updatedPSScriptFileContents = psScriptFileContents; + return successfullyUpdated; + } + #endregion #region Public Methods @@ -526,6 +672,7 @@ out ErrorRecord[] errors errors = errorsList.ToArray(); } + pSScriptFileString = String.Empty; return fileContentsSuccessfullyCreated; } @@ -592,7 +739,7 @@ out string psRequiresString psRequiresLines.Add("\n"); psRequiresString = String.Join("\n", psRequiresLines); - // TODO: ANAM where does the GUID come in? + // TODO: Does the GUID come in for ModuleSpecification(string) constructed object's ToString() output? } } diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 0e2e66a75..dbf8e200a 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -68,8 +68,9 @@ protected override void ProcessRecord() if (!PSScriptFileInfo.TryParseScriptFileInfo( scriptFileInfo: resolvedPath, parsedScript: out PSScriptFileInfo parsedScriptInfo, - moduleSpecErrors: out ErrorRecord[] errors)) + errors: out ErrorRecord[] errors)) { + 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); diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 390fe2781..668d1bb63 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -24,6 +24,7 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet private Uri _projectUri; private Uri _licenseUri; private Uri _iconUri; + private List validatedRequiredModuleSpecifications; #endregion @@ -108,7 +109,7 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet /// /// The path the .ps1 script info file will be created at /// - [Parameter(Position = 0)] + [Parameter(Position = 0, Mandatory = true)] [ValidateNotNullOrEmpty] public string Path { get; set; } @@ -192,84 +193,116 @@ protected override void ProcessRecord() ThrowTerminatingError(iconErrorRecord); } - // bool usePath = false; - // if (!String.IsNullOrEmpty(Path)) - // { - // if (!Path.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); - // } - // else if (File.Exists(Path) && !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); - // } - - // // if neither of those cases, Path is non-null and valid. - // usePath = true; - // } - // else if (PassThru) - // { - // usePath = false; - // } - // else - // { - // // Either valid Path or PassThru parameter must be supplied. - // var exMessage = "Either -Path parameter or -PassThru parameter value must be supplied to output script file contents to."; - // var ex = new ArgumentException(exMessage); - // var PathOrPassThruParameterRequiredError = new ErrorRecord(ex, "PathOrPassThruParameterRequired", ErrorCategory.InvalidArgument, null); - // ThrowTerminatingError(PathOrPassThruParameterRequiredError); - // } - - // PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( - // version: Version, - // guid: Guid, - // author: Author, - // companyName: CompanyName, - // copyright: Copyright, - // tags: Tags, - // licenseUri: _licenseUri, - // projectUri: _projectUri, - // iconUri: _iconUri, - // requiredModules: RequiredModules, - // externalModuleDependencies: ExternalModuleDependencies, - // requiredScripts: RequiredScripts, - // externalScriptDependencies: ExternalScriptDependencies, - // releaseNotes: ReleaseNotes, - // privateData: PrivateData, - // description: Description, - // cmdletPassedIn: this); - - // if (!currentScriptInfo.TryCreateScriptFileInfoString( - // pSScriptFileString: out string psScriptFileContents, - // errors: out ErrorRecord[] errors)) - // { - // foreach (ErrorRecord err in errors) - // { - // WriteError(err); - // } - - // return; - // // TODO: Anam, currently only one error and you return. So maybe this shouldn't be a list? - // // But for extensability makes sense. - // } - - // if (usePath) - // { - // File.WriteAllText(Path, psScriptFileContents); // TODO: Anam better way to do this? - // } - - // if (!usePath || PassThru) - // { - // // TODO: Anam do we also write to console if Path AND PassThru used together? - // WriteObject(psScriptFileContents); - // } + if (!Path.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) || !File.Exists(Path)) + { + var exMessage = "Path needs to exist and end with a .ps1 file. 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(Path); + 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 resolvedPath = resolvedPaths[0].Path; + + if (RequiredModules.Length > 0) + { + // TODO: ANAM have this return array not list for mod specs + Utils.CreateModuleSpecification( + moduleSpecHashtables: RequiredModules, + out validatedRequiredModuleSpecifications, + out ErrorRecord[] moduleSpecErrors); + if (moduleSpecErrors.Length > 0) + { + foreach (ErrorRecord err in moduleSpecErrors) + { + WriteError(err); + } + } + } + + // get PSScriptFileInfo object for current script contents + if (!PSScriptFileInfo.TryParseScriptFileInfo( + scriptFileInfoPath: resolvedPath, + out PSScriptFileInfo parsedScriptFileInfo, + out ErrorRecord[] errors)) + { + 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; // TODO: should this be a terminating error instead? + } + else + { + // update requested field + if (!PSScriptFileInfo.TryUpdateRequestedFields( + ref parsedScriptFileInfo, + out ErrorRecord[] updateErrors, + out string updatedPSScriptFileContents, + version: Version, + guid: Guid, + author: Author, + companyName: CompanyName, + copyright: Copyright, + tags: Tags, + licenseUri: _licenseUri, + projectUri: _projectUri, + iconUri: _iconUri, + requiredModules: validatedRequiredModuleSpecifications.ToArray(), + externalModuleDependencies: ExternalModuleDependencies, + requiredScripts: RequiredScripts, + externalScriptDependencies: ExternalScriptDependencies, + releaseNotes: ReleaseNotes, + privateData: PrivateData, + description: Description)) + { + WriteWarning("Could not update the specified file due to the following error(s):"); + foreach (ErrorRecord error in updateErrors) + { + WriteError(error); + } + } + else + { + // now have updated script contents as a string. + var tempScriptFilePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString(), "TempScript.ps1"); + File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); + + if (!PSScriptFileInfo.TryParseScriptFileInfo( + scriptFileInfoPath: tempScriptFilePath, + out PSScriptFileInfo updatedPSScriptInfo, + out ErrorRecord[] testErrors)) + { + WriteWarning("The updated test file created is invalid due to the following error(s):"); + foreach (ErrorRecord error in testErrors) + { + WriteError(error); + } + } + else + { + // write out updated script file's contents to original script file + // TODO: do I need to provide permissions here? + File.WriteAllText(resolvedPath, updatedPSScriptFileContents); + File.Delete(tempScriptFilePath); + if (PassThru) + { + WriteObject(updatedPSScriptInfo); + } + } + + } + } } #endregion From 015d4cd49b17491e89f2ff22d382732f9451030f Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 11 Feb 2022 13:47:43 -0500 Subject: [PATCH 20/86] add help docs; Update works except for RequiredModules population --- help/New-PSScriptFileInfo.md | 320 ++++++++++++++++++ help/Test-PSScriptFileInfo.md | 80 +++++ ...FileInfo.md => Update-PSScriptFileInfo.md} | 58 ++-- src/PowerShellGet.psd1 | 5 +- src/code/NewPSScriptFileInfo.cs | 4 +- src/code/PSScriptFileInfo.cs | 59 ++-- src/code/TestPSScriptFileInfo.cs | 2 +- src/code/UpdatePSScriptFileInfo.cs | 29 +- 8 files changed, 484 insertions(+), 73 deletions(-) create mode 100644 help/New-PSScriptFileInfo.md create mode 100644 help/Test-PSScriptFileInfo.md rename help/{New-ScriptFileInfo.md => Update-PSScriptFileInfo.md} (82%) diff --git a/help/New-PSScriptFileInfo.md b/help/New-PSScriptFileInfo.md new file mode 100644 index 000000000..47d1976e7 --- /dev/null +++ b/help/New-PSScriptFileInfo.md @@ -0,0 +1,320 @@ +--- +external help file: PowerShellGet.dll-Help.xml +Module Name: PowerShellGet +online version: +schema: 2.0.0 +--- + +# New-ScriptFileInfo + +## SYNOPSIS +Creates a new .ps1 file containing metadata for the script, which is used when publishing a script package. + +## SYNTAX + +``` +New-PSScriptFileInfo [-Path ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-WhatIf] [] +``` + +## DESCRIPTION +The New-PSScriptFileInfo cmdlet creates a .ps1 file containing metadata for the script. + +## EXAMPLES + + +## PARAMETERS + +### -Path +The path the .ps1 script info file will be created at. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: False +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Version +The version of the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Author +The author of the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Description +The description of the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -GUID +The GUID for the script. + +```yaml +Type: System.Guid +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -CompanyName +The name of the company owning the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Copyright +The copyright information for the script. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -RequiredModules +The list of modules required for the script. + +```yaml +Type: Microsoft.PowerShell.Commands.ModuleSpecification[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExternalModuleDependencies +The list of external module dependencies taken by this script + +```yaml +Type: System.String[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -RequiredScripts +The list of scripts required by the script + +```yaml +Type: System.String[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExternalScriptDependencies +The list of external script dependencies taken by this script + +```yaml +Type: System.String[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Tags +The tags associated with the script. + +```yaml +Type: System.String[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProjectUri +The Uri for the project associated with the script + +```yaml +Type: System.Uri +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LicenseUri +The Uri for the license associated with the script + +```yaml +Type: System.Uri +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -IconUri +The Uri for the icon associated with the script + +```yaml +Type: System.Uri +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ReleaseNotes +The release notes for the script + +```yaml +Type: System.String[] +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PrivateData +The private data associated with the script + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). + +## OUTPUTS + +### Microsoft.PowerShell.PowerShellGet.UtilClasses.PSResourceInfo +``` +PSScriptFileInfo : { + Version + Guid + Author + CompanyName + Copyright + Tags + LicenseUri + ProjectUri + IconUri + RequiredModules + ExternalModuleDependencies + RequiredScripts + ExternalScriptDependencies + ReleaseNotes + PrivateData + Description + Synopsis + Example + Inputs + Outputs + Notes + Links + Component + Role + Functionality +} +``` + +## NOTES + +## RELATED LINKS diff --git a/help/Test-PSScriptFileInfo.md b/help/Test-PSScriptFileInfo.md new file mode 100644 index 000000000..25b9271cc --- /dev/null +++ b/help/Test-PSScriptFileInfo.md @@ -0,0 +1,80 @@ +--- +external help file: PowerShellGet.dll-Help.xml +Module Name: PowerShellGet +online version: +schema: 2.0.0 +--- + +# New-ScriptFileInfo + +## SYNOPSIS +Tests a .ps1 file at the specified path to ensure it is valid. + +## SYNTAX + +``` +Tes-PSScriptFileInfo [-Path ] [] +``` + +## DESCRIPTION +The Test-PSScriptFileInfo cmdlet tests a .ps1 file at the specified path to ensure it is valid. + +## EXAMPLES + + +## PARAMETERS + +### -Path +The path the .ps1 script info file will be created at. + +```yaml +Type: System.String +Parameter Sets: +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). + +## OUTPUTS + +### Microsoft.PowerShell.PowerShellGet.UtilClasses.PSResourceInfo +``` +PSScriptFileInfo : { + Version + Guid + Author + CompanyName + Copyright + Tags + LicenseUri + ProjectUri + IconUri + RequiredModules + ExternalModuleDependencies + RequiredScripts + ExternalScriptDependencies + ReleaseNotes + PrivateData + Description + Synopsis + Example + Inputs + Outputs + Notes + Links + Component + Role + Functionality +} +``` + +## NOTES + +## RELATED LINKS diff --git a/help/New-ScriptFileInfo.md b/help/Update-PSScriptFileInfo.md similarity index 82% rename from help/New-ScriptFileInfo.md rename to help/Update-PSScriptFileInfo.md index 5ccdae874..c138000ce 100644 --- a/help/New-ScriptFileInfo.md +++ b/help/Update-PSScriptFileInfo.md @@ -8,12 +8,12 @@ schema: 2.0.0 # New-ScriptFileInfo ## SYNOPSIS -Returns resources (modules and scripts) installed on the machine via PowerShellGet. +Updates an existing .ps1 file with requested properties and ensures it's valid. ## SYNTAX ``` -New-ScriptFileInfo [-Path ] [] +Update-PSScriptFileInfo [-Path ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-WhatIf] [] [-Path ] [] ``` ## DESCRIPTION @@ -32,7 +32,7 @@ Type: System.String Parameter Sets: Aliases: -Required: ?? +Required: True Position: 0 Default value: None Accept pipeline input: False @@ -47,7 +47,7 @@ Type: System.String Parameter Sets: Aliases: -Required: ?? +Required: False Position: Named Default value: None Accept pipeline input: False @@ -62,7 +62,7 @@ Type: System.String Parameter Sets: Aliases: -Required: ?? +Required: False Position: Named Default value: None Accept pipeline input: False @@ -77,7 +77,7 @@ Type: System.String Parameter Sets: Aliases: -Required: ?? +Required: False Position: Named Default value: None Accept pipeline input: False @@ -92,7 +92,7 @@ Type: System.String Parameter Sets: Aliases: -Required: ?? +Required: False Position: Named Default value: None Accept pipeline input: False @@ -107,7 +107,7 @@ Type: System.String Parameter Sets: Aliases: -Required: ?? +Required: False Position: Named Default value: None Accept pipeline input: False @@ -133,7 +133,7 @@ Accept wildcard characters: False The list of modules required for the script. ```yaml -Type: System.String[] # this was Object[] in V2 TODO, determine type +Type: Microsoft.PowerShell.Commands.ModuleSpecification[] Parameter Sets: Aliases: @@ -286,32 +286,32 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ### Microsoft.PowerShell.PowerShellGet.UtilClasses.PSResourceInfo ``` -PSResourceInfo : { - AdditionalMetadata +PSScriptFileInfo : { + Version + Guid Author CompanyName Copyright - Dependencies - Description - IconUri - Includes - InstalledDate - InstalledLocation - IsPrerelease + Tags LicenseUri - Name - PackageManagementProvider - PowerShellGetFormatVersion - Prerelease ProjectUri - PublishedDate + IconUri + RequiredModules + ExternalModuleDependencies + RequiredScripts + ExternalScriptDependencies ReleaseNotes - Repository - RepositorySourceLocation - Tags - Type - UpdatedDate - Version + PrivateData + Description + Synopsis + Example + Inputs + Outputs + Notes + Links + Component + Role + Functionality } ``` diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index c3eb82d63..07be7df10 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -21,12 +21,13 @@ 'Register-PSResourceRepository', 'Save-PSResource', 'Set-PSResourceRepository', + 'New-PSScriptFileInfo', 'Publish-PSResource', + 'Test-PSScriptFileInfo', 'Uninstall-PSResource', 'Unregister-PSResourceRepository', 'Update-PSResource', - 'New-PSScriptFileInfo', - 'Test-PSScriptFileInfo') + 'Update-PSScriptFileInfo') VariablesToExport = 'PSGetPath' AliasesToExport = @('inmo', 'fimo', 'upmo', 'pumo') diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index af059e2e2..503ed7a67 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -25,7 +25,6 @@ public sealed class NewPSScriptFileInfo : PSCmdlet private Uri _projectUri; private Uri _licenseUri; private Uri _iconUri; - private List validatedRequiredModuleSpecifications; #endregion @@ -194,7 +193,8 @@ protected override void ProcessRecord() ThrowTerminatingError(iconErrorRecord); } - if (RequiredModules.Length > 0) + List validatedRequiredModuleSpecifications = new List(); + if (RequiredModules != null && RequiredModules.Length > 0) { // TODO: ANAM have this return array not list for mod specs Utils.CreateModuleSpecification( diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index c17f04849..9a8c54ebc 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -39,7 +39,6 @@ public sealed class PSScriptFileInfo /// /// the name of the company owning the script /// - [ValidateRange(0, 50)] public string CompanyName { get; set; } /// @@ -188,8 +187,7 @@ public PSScriptFileInfo( Uri licenseUri, Uri projectUri, Uri iconUri, - ModuleSpecification[] requiredModules, // TODO: in V2 this was Object[] - // Hashtable[] requiredModules, + ModuleSpecification[] requiredModules, string[] externalModuleDependencies, string[] requiredScripts, string[] externalScriptDependencies, @@ -223,31 +221,6 @@ string description #endregion - /** - 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 - #> - */ - #region Public Static Methods public static bool TryParseScriptFileInfo( @@ -510,7 +483,7 @@ public static bool TryUpdateRequestedFields( return successfullyUpdated; } - PSScriptFileInfo tempScriptFileInfoObject = new PSScriptFileInfo(); + PSScriptFileInfo tempScriptFileInfoObject = parsedScript; // create new PSScriptFileInfo with updated fields try @@ -562,6 +535,7 @@ public static bool TryUpdateRequestedFields( } if (requiredModules != null && requiredModules.Length != 0){ + Console.WriteLine("made it to reset requiredmodules"); tempScriptFileInfoObject.RequiredModules = requiredModules; } @@ -605,7 +579,7 @@ public static bool TryUpdateRequestedFields( // create string contents for .ps1 file - if (tempScriptFileInfoObject.TryCreateScriptFileInfoString( + if (!tempScriptFileInfoObject.TryCreateScriptFileInfoString( pSScriptFileString: out string psScriptFileContents, errors: out ErrorRecord[] createFileContentErrors)) { @@ -691,6 +665,31 @@ out ErrorRecord error 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 + #> + */ + if (String.IsNullOrEmpty(Author) || Version == null) { var exMessage = "PSScriptInfo must contain values for Author and Version. Ensure both of these are present."; diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index dbf8e200a..5ebd03a59 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -66,7 +66,7 @@ protected override void ProcessRecord() var resolvedPath = resolvedPaths[0].Path; if (!PSScriptFileInfo.TryParseScriptFileInfo( - scriptFileInfo: resolvedPath, + scriptFileInfoPath: resolvedPath, parsedScript: out PSScriptFileInfo parsedScriptInfo, errors: out ErrorRecord[] errors)) { diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 668d1bb63..2c44ddc01 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -1,4 +1,3 @@ -using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -24,7 +23,6 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet private Uri _projectUri; private Uri _licenseUri; private Uri _iconUri; - private List validatedRequiredModuleSpecifications; #endregion @@ -54,7 +52,7 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet /// /// The description of the script /// - [Parameter(Mandatory = true)] + [Parameter()] [ValidateNotNullOrEmpty()] public string Description { get; set; } @@ -211,8 +209,9 @@ protected override void ProcessRecord() } string resolvedPath = resolvedPaths[0].Path; - - if (RequiredModules.Length > 0) + + List validatedRequiredModuleSpecifications = new List(); + if (RequiredModules != null && RequiredModules.Length > 0) { // TODO: ANAM have this return array not list for mod specs Utils.CreateModuleSpecification( @@ -274,8 +273,18 @@ protected override void ProcessRecord() } else { + WriteVerbose("scriptFileContents: \n" + updatedPSScriptFileContents); // now have updated script contents as a string. - var tempScriptFilePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString(), "TempScript.ps1"); + var tempScriptDirPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempScriptFilePath = System.IO.Path.Combine(tempScriptDirPath, "tempScript.ps1"); + if (!Directory.Exists(tempScriptFilePath)) + { + Directory.CreateDirectory(tempScriptFilePath); + } + + WriteObject(updatedPSScriptFileContents); + + File.Create(tempScriptFilePath); File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); if (!PSScriptFileInfo.TryParseScriptFileInfo( @@ -292,9 +301,11 @@ protected override void ProcessRecord() else { // write out updated script file's contents to original script file - // TODO: do I need to provide permissions here? - File.WriteAllText(resolvedPath, updatedPSScriptFileContents); - File.Delete(tempScriptFilePath); + // TODO: fix permissions to write to temp folder! + // File.WriteAllText(resolvedPath, updatedPSScriptFileContents); + // File.Delete(tempScriptFilePath); + // Utils.DeleteDirectory(tempScriptDirPath); + if (PassThru) { WriteObject(updatedPSScriptInfo); From 22156a2e47a24ca5236a36fb83b5e2ebfbefc554 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 11 Feb 2022 13:54:46 -0500 Subject: [PATCH 21/86] add comemnts --- src/code/PSScriptFileInfo.cs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 9a8c54ebc..b4d01176e 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -223,6 +223,9 @@ string description #region Public Static Methods + /// + /// Tests the contents of the .ps1 file at the provided path + /// public static bool TryParseScriptFileInfo( string scriptFileInfoPath, out PSScriptFileInfo parsedScript, @@ -447,6 +450,10 @@ public static bool TryParseScriptFileInfo( return successfullyParsed; } + /// + /// 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 + /// public static bool TryUpdateRequestedFields( ref PSScriptFileInfo parsedScript, out ErrorRecord[] errors, @@ -597,6 +604,10 @@ public static bool TryUpdateRequestedFields( #endregion #region Public Methods + + /// + /// Create .ps1 file contents with PSScriptFileInfo object's properties and output content as a string + /// public bool TryCreateScriptFileInfoString( out string pSScriptFileString, out ErrorRecord[] errors @@ -656,6 +667,10 @@ out ErrorRecord[] errors return fileContentsSuccessfullyCreated; } + /// + /// Used when creating .ps1 file's contents. + /// This creates the <#PSScriptInfo ... #> comment string + /// public bool GetPSScriptInfoString( out string pSScriptInfoString, out ErrorRecord error @@ -721,6 +736,10 @@ Feature 5 return pSScriptInfoSuccessfullyCreated; } + /// + /// Used when creating .ps1 file's contents. + /// This creates the #Requires comment string + /// public void GetRequiresString( out string psRequiresString ) @@ -742,6 +761,10 @@ out string psRequiresString } } + /// + /// Used when creating .ps1 file's contents. + /// This creates the help comment string: <# \n .DESCRIPTION #> + /// public bool GetScriptCommentHelpInfo( out string psHelpInfo, out ErrorRecord error @@ -816,6 +839,5 @@ out ErrorRecord error } #endregion - } } \ No newline at end of file From b4418745bbe75822be556bc8ba047e0f1c80481b Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 14 Feb 2022 21:50:26 -0500 Subject: [PATCH 22/86] fix bug where update doesn't retain requiredModules --- help/New-PSScriptFileInfo.md | 6 +++--- help/Test-PSScriptFileInfo.md | 4 ++-- help/Update-PSScriptFileInfo.md | 7 +++---- src/code/NewPSScriptFileInfo.cs | 4 +--- src/code/PSScriptFileInfo.cs | 13 ++++++++----- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/help/New-PSScriptFileInfo.md b/help/New-PSScriptFileInfo.md index 47d1976e7..72d22393d 100644 --- a/help/New-PSScriptFileInfo.md +++ b/help/New-PSScriptFileInfo.md @@ -13,7 +13,7 @@ Creates a new .ps1 file containing metadata for the script, which is used when p ## SYNTAX ``` -New-PSScriptFileInfo [-Path ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-WhatIf] [] +New-PSScriptFileInfo [-FilePath ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-PassThru] [-WhatIf] [] ``` ## DESCRIPTION @@ -24,7 +24,7 @@ The New-PSScriptFileInfo cmdlet creates a .ps1 file containing metadata for the ## PARAMETERS -### -Path +### -FilePath The path the .ps1 script info file will be created at. ```yaml @@ -32,7 +32,7 @@ Type: System.String Parameter Sets: Aliases: -Required: False +Required: True Position: 0 Default value: None Accept pipeline input: False diff --git a/help/Test-PSScriptFileInfo.md b/help/Test-PSScriptFileInfo.md index 25b9271cc..543d3615a 100644 --- a/help/Test-PSScriptFileInfo.md +++ b/help/Test-PSScriptFileInfo.md @@ -13,7 +13,7 @@ Tests a .ps1 file at the specified path to ensure it is valid. ## SYNTAX ``` -Tes-PSScriptFileInfo [-Path ] [] +Test-PSScriptFileInfo [-FilePath ] [] ``` ## DESCRIPTION @@ -24,7 +24,7 @@ The Test-PSScriptFileInfo cmdlet tests a .ps1 file at the specified path to ensu ## PARAMETERS -### -Path +### -FilePath The path the .ps1 script info file will be created at. ```yaml diff --git a/help/Update-PSScriptFileInfo.md b/help/Update-PSScriptFileInfo.md index c138000ce..243976948 100644 --- a/help/Update-PSScriptFileInfo.md +++ b/help/Update-PSScriptFileInfo.md @@ -13,18 +13,17 @@ Updates an existing .ps1 file with requested properties and ensures it's valid. ## SYNTAX ``` -Update-PSScriptFileInfo [-Path ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-WhatIf] [] [-Path ] [] +Update-PSScriptFileInfo [-FilePath ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-WhatIf] [-Validate] [] [] ``` ## DESCRIPTION -The Get-PSResource cmdlet combines the Get-InstalledModule, Get-InstalledScript cmdlets from V2. It performs a search within module or script installation paths based on the -Name parameter argument. It returns PSResourceInfo objects which describes each resource item found. Other parameters allow the returned results to be filtered by version and path. +TODOs ## EXAMPLES - ## PARAMETERS -### -Path +### -FilePath The path the .ps1 script info file will be created at. ```yaml diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 503ed7a67..ef9f4d954 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -1,5 +1,3 @@ -using System.Linq; -using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -33,7 +31,7 @@ public sealed class NewPSScriptFileInfo : PSCmdlet /// /// The path the .ps1 script info file will be created at /// - [Parameter(Position = 0)] + [Parameter(Position = 0, Mandatory = true)] [ValidateNotNullOrEmpty] public string Path { get; set; } diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index b4d01176e..301171eca 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -339,11 +339,14 @@ public static bool TryParseScriptFileInfo( if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) { - ReadOnlyCollection parsedRequiredModules = parsedScriptRequirements.RequiredModules; - if (parsedPSScriptInfoHashtable.ContainsKey("RequiredModules")) - { - parsedModules = (ReadOnlyCollection) parsedPSScriptInfoHashtable["RequiredModules"]; - } + // ReadOnlyCollection parsedRequiredModules = parsedScriptRequirements.RequiredModules; + parsedModules = parsedScriptRequirements.RequiredModules; + // TODO: Anam was there any importance for this? + // if (parsedPSScriptInfoHashtable.ContainsKey("RequiredModules")) + // { + // parsedModules = (ReadOnlyCollection) parsedPSScriptInfoHashtable["RequiredModules"]; + // } + parsedPSScriptInfoHashtable.Add("RequiredModules", parsedModules); } // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow From cdd52d68420a94004ef17742e66d3518cafe6a7c Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 14 Feb 2022 21:56:26 -0500 Subject: [PATCH 23/86] New-PSScriptFileInfo cmdlet: rename Path to FilePath and make FilePath mandatory --- src/code/NewPSScriptFileInfo.cs | 75 ++++++++++++++------------------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index ef9f4d954..d34e575e2 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -33,7 +33,7 @@ public sealed class NewPSScriptFileInfo : PSCmdlet /// [Parameter(Position = 0, Mandatory = true)] [ValidateNotNullOrEmpty] - public string Path { get; set; } + public string FilePath { get; set; } /// /// The version of the script @@ -191,6 +191,33 @@ protected override void ProcessRecord() 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); + } + else if (File.Exists(FilePath) && !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); + } + + 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; + List validatedRequiredModuleSpecifications = new List(); if (RequiredModules != null && RequiredModules.Length > 0) { @@ -208,41 +235,6 @@ protected override void ProcessRecord() } } - bool usePath = false; - if (!String.IsNullOrEmpty(Path)) - { - if (!Path.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); - } - else if (File.Exists(Path) && !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); - } - - // if neither of those cases, Path is non-null and valid. - usePath = true; - } - else if (PassThru) - { - usePath = false; - } - else - { - // Either valid Path or PassThru parameter must be supplied. - var exMessage = "Either -Path parameter or -PassThru parameter value must be supplied to output script file contents to."; - var ex = new ArgumentException(exMessage); - var PathOrPassThruParameterRequiredError = new ErrorRecord(ex, "PathOrPassThruParameterRequired", ErrorCategory.InvalidArgument, null); - ThrowTerminatingError(PathOrPassThruParameterRequiredError); - } - PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( version: Version, guid: Guid, @@ -254,7 +246,6 @@ protected override void ProcessRecord() projectUri: _projectUri, iconUri: _iconUri, requiredModules: validatedRequiredModuleSpecifications.ToArray(), - // requiredModules: RequiredModules, externalModuleDependencies: ExternalModuleDependencies, requiredScripts: RequiredScripts, externalScriptDependencies: ExternalScriptDependencies, @@ -276,14 +267,10 @@ protected override void ProcessRecord() // But for extensability makes sense. } - if (usePath) - { - File.WriteAllText(Path, psScriptFileContents); // TODO: Anam better way to do this? - } - - if (!usePath || PassThru) + File.WriteAllText(resolvedFilePath, psScriptFileContents); // TODO: Anam better way to do this? + + if (PassThru) { - // TODO: Anam do we also write to console if Path AND PassThru used together? WriteObject(psScriptFileContents); } } From f4a205d2b486fc7ce410660db04e2bfd29619d36 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 14 Feb 2022 22:00:09 -0500 Subject: [PATCH 24/86] update help file for New-PSScriptFileInfo cmdlet --- help/New-PSScriptFileInfo.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/help/New-PSScriptFileInfo.md b/help/New-PSScriptFileInfo.md index 72d22393d..25682209f 100644 --- a/help/New-PSScriptFileInfo.md +++ b/help/New-PSScriptFileInfo.md @@ -13,7 +13,7 @@ Creates a new .ps1 file containing metadata for the script, which is used when p ## SYNTAX ``` -New-PSScriptFileInfo [-FilePath ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-PassThru] [-WhatIf] [] +New-PSScriptFileInfo [-FilePath ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-PassThru] [-Force] [-WhatIf] [] ``` ## DESCRIPTION @@ -279,6 +279,36 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -PassThru +When specified, displays the PSScriptFileInfo object representing the script metadata. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force +When specified, if the file at the specified -FilePath exists it overwrites that file. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). From b07fab25510419c05e093ecc88cc2335f51cc1af Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 14 Feb 2022 22:04:50 -0500 Subject: [PATCH 25/86] Test-PSScriptFileInfo cmdlet: rename Path to FilePath --- src/code/TestPSScriptFileInfo.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 5ebd03a59..81cecf213 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -1,4 +1,3 @@ -using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -29,7 +28,7 @@ public sealed class TestPSScriptFileInfo : PSCmdlet /// [Parameter(Position = 0, Mandatory = true)] [ValidateNotNullOrEmpty] - public string Path { get; set; } + public string FilePath { get; set; } #endregion @@ -37,16 +36,15 @@ public sealed class TestPSScriptFileInfo : PSCmdlet protected override void ProcessRecord() { - if (!File.Exists(Path)) + if (!File.Exists(FilePath)) { - // .ps1 file at specified location already exists and Force parameter isn't used to rewrite the file 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); } - if (!Path.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + 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); @@ -54,7 +52,7 @@ protected override void ProcessRecord() ThrowTerminatingError(InvalidPathError); } - var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(Path); + var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); if (resolvedPaths.Count != 1) { var exMessage = "Error: Could not resolve provided Path argument into a single path."; @@ -63,10 +61,10 @@ protected override void ProcessRecord() ThrowTerminatingError(InvalidPathArgumentError); } - var resolvedPath = resolvedPaths[0].Path; + var resolvedFilePath = resolvedPaths[0].Path; if (!PSScriptFileInfo.TryParseScriptFileInfo( - scriptFileInfoPath: resolvedPath, + scriptFileInfoPath: resolvedFilePath, parsedScript: out PSScriptFileInfo parsedScriptInfo, errors: out ErrorRecord[] errors)) { From ce1ae070c6f2937a69f60f73b16411eed7e6c756 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 14 Feb 2022 22:08:11 -0500 Subject: [PATCH 26/86] Update-PSScriptFileInfo cmdlet: change Path to FilePath --- help/Update-PSScriptFileInfo.md | 2 +- src/code/UpdatePSScriptFileInfo.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/help/Update-PSScriptFileInfo.md b/help/Update-PSScriptFileInfo.md index 243976948..0ed54b071 100644 --- a/help/Update-PSScriptFileInfo.md +++ b/help/Update-PSScriptFileInfo.md @@ -17,7 +17,7 @@ Update-PSScriptFileInfo [-FilePath ] [-Version ] [-Author [Parameter(Position = 0, Mandatory = true)] [ValidateNotNullOrEmpty] - public string Path { get; set; } + public string FilePath { get; set; } /// /// The private data associated with the script @@ -191,7 +191,7 @@ protected override void ProcessRecord() ThrowTerminatingError(iconErrorRecord); } - if (!Path.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) || !File.Exists(Path)) + if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) || !File.Exists(FilePath)) { var exMessage = "Path needs to exist and end with a .ps1 file. Example: C:/Users/john/x/MyScript.ps1"; var ex = new ArgumentException(exMessage); @@ -199,7 +199,7 @@ protected override void ProcessRecord() ThrowTerminatingError(InvalidOrNonExistantPathError); } - var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(Path); + var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); if (resolvedPaths.Count != 1) { var exMessage = "Error: Could not resolve provided Path argument into a single path."; @@ -208,7 +208,7 @@ protected override void ProcessRecord() ThrowTerminatingError(InvalidPathArgumentError); } - string resolvedPath = resolvedPaths[0].Path; + string resolvedFilePath = resolvedPaths[0].Path; List validatedRequiredModuleSpecifications = new List(); if (RequiredModules != null && RequiredModules.Length > 0) @@ -229,7 +229,7 @@ protected override void ProcessRecord() // get PSScriptFileInfo object for current script contents if (!PSScriptFileInfo.TryParseScriptFileInfo( - scriptFileInfoPath: resolvedPath, + scriptFileInfoPath: resolvedFilePath, out PSScriptFileInfo parsedScriptFileInfo, out ErrorRecord[] errors)) { @@ -275,8 +275,8 @@ protected override void ProcessRecord() { WriteVerbose("scriptFileContents: \n" + updatedPSScriptFileContents); // now have updated script contents as a string. - var tempScriptDirPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString()); - var tempScriptFilePath = System.IO.Path.Combine(tempScriptDirPath, "tempScript.ps1"); + var tempScriptDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempScriptFilePath = Path.Combine(tempScriptDirPath, "tempScript.ps1"); if (!Directory.Exists(tempScriptFilePath)) { Directory.CreateDirectory(tempScriptFilePath); From 4cb3e0f06ba47ffb19b1ec0a5bcdf09db68de5ba Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 15 Feb 2022 14:30:52 -0500 Subject: [PATCH 27/86] gets end of file ast content; some duplication issues though still --- src/code/PSScriptFileInfo.cs | 115 ++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 301171eca..54f86a83a 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -1,3 +1,4 @@ +using System.Runtime.Serialization; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -172,6 +173,26 @@ public sealed class PSScriptFileInfo [ValidateNotNullOrEmpty()] public string[] Functionality { get; set; } = new string[]{}; + /// + /// Optional end of file contents describing parameters + /// + public ParamBlockAst ParamBlock { get; set; } + + /// + /// Optional end of file contents describing begin block + /// + private NamedBlockAst BeginBlock { get; set; } + + /// + /// Optional end of file contents describing process block + /// + private NamedBlockAst ProcessBlock { get; set; } + + /// + /// Optional end of file contents describing end block + /// + private NamedBlockAst EndBlock { get; set; } + #endregion @@ -371,6 +392,37 @@ public static bool TryParseScriptFileInfo( parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); } + // // see if there's any additional content in the file + // string optionalEndOfFileContents = String.Empty; + // if (ast.ParamBlock != null) + // { + + // if (ast.ParamBlock.Attributes != null && ast.ParamBlock.Attributes.Count > 0 && ast.ParamBlock.Attributes[0].Extent != null) + // { + // optionalEndOfFileContents += "\n" + ast.ParamBlock.Attributes[0].Extent.Text; + // } + + // if (ast.ParamBlock.Extent != null) + // { + // optionalEndOfFileContents += "\n" + ast.ParamBlock.Extent.Text; + // } + // } + + // if (ast.BeginBlock != null) + // { + // optionalEndOfFileContents += "\n" + ast.BeginBlock.Extent.Text; + // } + + // if (ast.ProcessBlock != null) + // { + // optionalEndOfFileContents += "\n" + ast.ProcessBlock.Extent.Text; + // } + + // if (ast.EndBlock != null) + // { + // optionalEndOfFileContents += "\n" + ast.EndBlock.Extent.Text; + // } + string parsedVersion = (string) parsedPSScriptInfoHashtable["VERSION"]; string parsedAuthor = (string) parsedPSScriptInfoHashtable["AUTHOR"]; @@ -430,6 +482,27 @@ public static bool TryParseScriptFileInfo( releaseNotes: parsedReleaseNotes, privateData: (string) parsedPSScriptInfoHashtable["PRIVATEDATA"], description: scriptCommentInfo.Description); + + // populate Ast block related properties in case the file has end of file content + if (ast.ParamBlock != null) + { + parsedScript.ParamBlock = ast.ParamBlock; + } + + if (ast.BeginBlock != null) + { + parsedScript.BeginBlock = ast.BeginBlock; + } + + if (ast.ProcessBlock != null) + { + parsedScript.ProcessBlock = ast.ProcessBlock; + } + + if (ast.EndBlock != null) + { + parsedScript.EndBlock = ast.EndBlock; + } } catch (Exception e) { @@ -545,7 +618,6 @@ public static bool TryUpdateRequestedFields( } if (requiredModules != null && requiredModules.Length != 0){ - Console.WriteLine("made it to reset requiredmodules"); tempScriptFileInfoObject.RequiredModules = requiredModules; } @@ -599,6 +671,7 @@ public static bool TryUpdateRequestedFields( } // TODO: must also add Ast.EndBlock.Extent.Text or last n lines from file. + successfullyUpdated = true; updatedPSScriptFileContents = psScriptFileContents; return successfullyUpdated; @@ -666,6 +739,12 @@ out ErrorRecord[] errors pSScriptFileString += "\n" + psHelpInfo; + GetEndOfFileContent(out string endOfFileAstContent); + if (!String.IsNullOrEmpty(endOfFileAstContent)) + { + pSScriptFileString += "\n" + endOfFileAstContent; + } + fileContentsSuccessfullyCreated = true; return fileContentsSuccessfullyCreated; } @@ -841,6 +920,40 @@ out ErrorRecord error return psHelpInfoSuccessfullyCreated; } + public void GetEndOfFileContent( + out string endOfFileContent + ) + { + endOfFileContent = String.Empty; + if (ParamBlock != null) + { + if (ParamBlock.Attributes != null && ParamBlock.Attributes.Count > 0 && ParamBlock.Attributes[0].Extent != null) + { + endOfFileContent += "\n" + ParamBlock.Attributes[0].Extent.Text; + } + + if (ParamBlock.Extent != null) + { + endOfFileContent += "\n" + ParamBlock.Extent.Text; + } + } + + if (BeginBlock != null) + { + endOfFileContent += "\n" + BeginBlock.Extent.Text; + } + + if (ProcessBlock != null) + { + endOfFileContent += "\n" + ProcessBlock.Extent.Text; + } + + if (EndBlock != null) + { + endOfFileContent += "\n" + EndBlock.Extent.Text; + } + } + #endregion } } \ No newline at end of file From afaeac0f86f44628dda2cd0a1b72cb5efe7f5c75 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 16 Feb 2022 11:46:24 -0500 Subject: [PATCH 28/86] refactor update to pass in regalar argument for original script and out variable for udpated one --- src/code/PSScriptFileInfo.cs | 50 +++++++------- src/code/TestPSScriptFileInfo.cs | 8 ++- src/code/UpdatePSScriptFileInfo.cs | 104 ++++++++++++++++++++++------- 3 files changed, 110 insertions(+), 52 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 54f86a83a..4f39b9cbd 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -531,9 +531,10 @@ public static bool TryParseScriptFileInfo( /// and writes new updated script file contents to a string and updates the original PSScriptFileInfo object /// public static bool TryUpdateRequestedFields( - ref PSScriptFileInfo parsedScript, - out ErrorRecord[] errors, + PSScriptFileInfo originalScript, + out PSScriptFileInfo updatedScript, out string updatedPSScriptFileContents, + out ErrorRecord[] errors, string version, Guid guid, string author, @@ -552,11 +553,12 @@ public static bool TryUpdateRequestedFields( string description) { bool successfullyUpdated = false; + updatedScript = null; updatedPSScriptFileContents = String.Empty; errors = new ErrorRecord[]{}; List errorsList = new List(); - if (parsedScript == null) + if (originalScript == null) { var message = String.Format("PSScriptFileInfo object to update is null."); var ex = new ArgumentException(message); @@ -566,7 +568,9 @@ public static bool TryUpdateRequestedFields( return successfullyUpdated; } - PSScriptFileInfo tempScriptFileInfoObject = parsedScript; + originalScript = null; + + updatedScript = originalScript; // create new PSScriptFileInfo with updated fields try @@ -575,78 +579,78 @@ public static bool TryUpdateRequestedFields( { if (!System.Version.TryParse(version, out Version updatedVersion)) { - tempScriptFileInfoObject.Version = new Version("2.0.0.0"); + updatedScript.Version = new Version("2.0.0.0"); } else { - tempScriptFileInfoObject.Version = updatedVersion; + updatedScript.Version = updatedVersion; } } if (guid != Guid.Empty) { - tempScriptFileInfoObject.Guid = guid; + updatedScript.Guid = guid; } if (!String.IsNullOrEmpty(author)) { - tempScriptFileInfoObject.Author = author; + updatedScript.Author = author; } if (!String.IsNullOrEmpty(companyName)){ - tempScriptFileInfoObject.CompanyName = companyName; + updatedScript.CompanyName = companyName; } if (!String.IsNullOrEmpty(copyright)){ - tempScriptFileInfoObject.Copyright = copyright; + updatedScript.Copyright = copyright; } if (tags != null && tags.Length != 0){ - tempScriptFileInfoObject.Tags = tags; + updatedScript.Tags = tags; } if (licenseUri != null && !licenseUri.Equals(default(Uri))){ - tempScriptFileInfoObject.LicenseUri = licenseUri; + updatedScript.LicenseUri = licenseUri; } if (projectUri != null && !projectUri.Equals(default(Uri))){ - tempScriptFileInfoObject.ProjectUri = projectUri; + updatedScript.ProjectUri = projectUri; } if (iconUri != null && !iconUri.Equals(default(Uri))){ - tempScriptFileInfoObject.IconUri = iconUri; + updatedScript.IconUri = iconUri; } if (requiredModules != null && requiredModules.Length != 0){ - tempScriptFileInfoObject.RequiredModules = requiredModules; + updatedScript.RequiredModules = requiredModules; } if (externalModuleDependencies != null && externalModuleDependencies.Length != 0){ - tempScriptFileInfoObject.ExternalModuleDependencies = externalModuleDependencies; + updatedScript.ExternalModuleDependencies = externalModuleDependencies; } if (requiredScripts != null && requiredScripts.Length != 0) { - tempScriptFileInfoObject.RequiredScripts = requiredScripts; + updatedScript.RequiredScripts = requiredScripts; } if (externalScriptDependencies != null && externalScriptDependencies.Length != 0){ - tempScriptFileInfoObject.ExternalScriptDependencies = externalScriptDependencies; + updatedScript.ExternalScriptDependencies = externalScriptDependencies; } if (releaseNotes != null && releaseNotes.Length != 0) { - tempScriptFileInfoObject.ReleaseNotes = releaseNotes; + updatedScript.ReleaseNotes = releaseNotes; } if (!String.IsNullOrEmpty(privateData)) { - tempScriptFileInfoObject.PrivateData = privateData; + updatedScript.PrivateData = privateData; } if (!String.IsNullOrEmpty(description)) { - tempScriptFileInfoObject.Description = description; + updatedScript.Description = description; } } catch (Exception exception) @@ -661,7 +665,7 @@ public static bool TryUpdateRequestedFields( // create string contents for .ps1 file - if (!tempScriptFileInfoObject.TryCreateScriptFileInfoString( + if (!updatedScript.TryCreateScriptFileInfoString( pSScriptFileString: out string psScriptFileContents, errors: out ErrorRecord[] createFileContentErrors)) { @@ -670,8 +674,6 @@ public static bool TryUpdateRequestedFields( return successfullyUpdated; } - // TODO: must also add Ast.EndBlock.Extent.Text or last n lines from file. - successfullyUpdated = true; updatedPSScriptFileContents = psScriptFileContents; return successfullyUpdated; diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 81cecf213..a06334641 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -19,6 +19,7 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets /// Returns a single resource or multiple resource. /// [Cmdlet(VerbsDiagnostic.Test, "PSScriptFileInfo")] + [OutputType(typeof(bool))] public sealed class TestPSScriptFileInfo : PSCmdlet { #region Parameters @@ -68,15 +69,16 @@ protected override void ProcessRecord() parsedScript: out PSScriptFileInfo parsedScriptInfo, errors: out ErrorRecord[] errors)) { - 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); + WriteWarning("The .ps1 script file passed in was not valid due to: " + error); } + // return false; } else { - WriteObject(parsedScriptInfo); + // WriteObject(parsedScriptInfo); + // return true; } } diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index d9153ae02..9e06f5f24 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -153,6 +153,12 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet [ValidateNotNullOrEmpty()] public string[] Tags { get; set; } + /// + /// If specified, it validates the updated script + /// + [Parameter] + public SwitchParameter Validate { get; set; } + /// /// The version of the script /// @@ -245,9 +251,10 @@ protected override void ProcessRecord() { // update requested field if (!PSScriptFileInfo.TryUpdateRequestedFields( - ref parsedScriptFileInfo, - out ErrorRecord[] updateErrors, + parsedScriptFileInfo, // TODO: ANam change this to be out, or if we keep the same scenario pass as regular arg + out PSScriptFileInfo updatedScript, out string updatedPSScriptFileContents, + out ErrorRecord[] updateErrors, version: Version, guid: Guid, author: Author, @@ -273,8 +280,9 @@ protected override void ProcessRecord() } else { - WriteVerbose("scriptFileContents: \n" + updatedPSScriptFileContents); - // now have updated script contents as a string. + parsedScriptFileInfo = updatedScript; + + // write string of file contents to a temp file var tempScriptDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); var tempScriptFilePath = Path.Combine(tempScriptDirPath, "tempScript.ps1"); if (!Directory.Exists(tempScriptFilePath)) @@ -282,37 +290,83 @@ protected override void ProcessRecord() Directory.CreateDirectory(tempScriptFilePath); } - WriteObject(updatedPSScriptFileContents); - File.Create(tempScriptFilePath); File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); + WriteObject(updatedPSScriptFileContents); - if (!PSScriptFileInfo.TryParseScriptFileInfo( - scriptFileInfoPath: tempScriptFilePath, - out PSScriptFileInfo updatedPSScriptInfo, - out ErrorRecord[] testErrors)) + if (Validate) { - WriteWarning("The updated test file created is invalid due to the following error(s):"); - foreach (ErrorRecord error in testErrors) + if (!PSScriptFileInfo.TryParseScriptFileInfo( + scriptFileInfoPath: tempScriptFilePath, + out parsedScriptFileInfo, + out ErrorRecord[] testErrors)) { - WriteError(error); + WriteWarning("The updated test file created is invalid due to the following error(s):"); + foreach (ErrorRecord error in testErrors) + { + WriteError(error); + } + return; // TODO: Anam do we need this } } - else - { - // write out updated script file's contents to original script file - // TODO: fix permissions to write to temp folder! - // File.WriteAllText(resolvedPath, updatedPSScriptFileContents); - // File.Delete(tempScriptFilePath); - // Utils.DeleteDirectory(tempScriptDirPath); - if (PassThru) - { - WriteObject(updatedPSScriptInfo); - } - } + // write out updated script file's contents to original script file + // TODO: fix permissions to write to temp folder! + // File.WriteAllText(resolvedPath, updatedPSScriptFileContents); + // File.Delete(tempScriptFilePath); + // Utils.DeleteDirectory(tempScriptDirPath); + if (PassThru) + { + WriteObject(parsedScriptFileInfo); + WriteObject(updatedScript); + } } + // else + // { + // WriteVerbose("scriptFileContents: \n" + updatedPSScriptFileContents); // TODO: Anam remove + + // // write string of file contents to a temp file + // var tempScriptDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + // var tempScriptFilePath = Path.Combine(tempScriptDirPath, "tempScript.ps1"); + // if (!Directory.Exists(tempScriptFilePath)) + // { + // Directory.CreateDirectory(tempScriptFilePath); + // } + + // File.Create(tempScriptFilePath); + // File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); + // WriteObject(updatedPSScriptFileContents); // TODO: Anam remove + + // // if Validate, additionally validate this file's contents + // // if testing fails write errors and return-ish + // // if testing passes continue to copy temp file contents to permenant file and check for passthru + // if (!PSScriptFileInfo.TryParseScriptFileInfo( + // scriptFileInfoPath: tempScriptFilePath, + // out PSScriptFileInfo updatedPSScriptInfo, + // out ErrorRecord[] testErrors)) + // { + // WriteWarning("The updated test file created is invalid due to the following error(s):"); + // foreach (ErrorRecord error in testErrors) + // { + // WriteError(error); + // } + // } + // else + // { + // // write out updated script file's contents to original script file + // // TODO: fix permissions to write to temp folder! + // // File.WriteAllText(resolvedPath, updatedPSScriptFileContents); + // // File.Delete(tempScriptFilePath); + // // Utils.DeleteDirectory(tempScriptDirPath); + + // if (PassThru) + // { + // WriteObject(updatedPSScriptInfo); + // } + // } + + // } } } From 70aba507b531cdee703815e11630dabd8b439481 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 16 Feb 2022 13:15:35 -0500 Subject: [PATCH 29/86] refactor udpate to use ref variable to pass PSScriptFileInfo --- src/code/PSScriptFileInfo.cs | 11 +++++------ src/code/UpdatePSScriptFileInfo.cs | 14 ++++++-------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 4f39b9cbd..332fb7d91 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -531,8 +531,7 @@ public static bool TryParseScriptFileInfo( /// and writes new updated script file contents to a string and updates the original PSScriptFileInfo object /// public static bool TryUpdateRequestedFields( - PSScriptFileInfo originalScript, - out PSScriptFileInfo updatedScript, + ref PSScriptFileInfo originalScript, out string updatedPSScriptFileContents, out ErrorRecord[] errors, string version, @@ -553,7 +552,7 @@ public static bool TryUpdateRequestedFields( string description) { bool successfullyUpdated = false; - updatedScript = null; + // updatedScript = null; updatedPSScriptFileContents = String.Empty; errors = new ErrorRecord[]{}; List errorsList = new List(); @@ -568,9 +567,7 @@ public static bool TryUpdateRequestedFields( return successfullyUpdated; } - originalScript = null; - - updatedScript = originalScript; + PSScriptFileInfo updatedScript = originalScript; // create new PSScriptFileInfo with updated fields try @@ -652,6 +649,8 @@ public static bool TryUpdateRequestedFields( { updatedScript.Description = description; } + + originalScript = updatedScript; } catch (Exception exception) { diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 9e06f5f24..0d2ec89e8 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -251,8 +251,7 @@ protected override void ProcessRecord() { // update requested field if (!PSScriptFileInfo.TryUpdateRequestedFields( - parsedScriptFileInfo, // TODO: ANam change this to be out, or if we keep the same scenario pass as regular arg - out PSScriptFileInfo updatedScript, + ref parsedScriptFileInfo, out string updatedPSScriptFileContents, out ErrorRecord[] updateErrors, version: Version, @@ -279,12 +278,12 @@ protected override void ProcessRecord() } } else - { - parsedScriptFileInfo = updatedScript; - + { // write string of file contents to a temp file - var tempScriptDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var tempScriptFilePath = Path.Combine(tempScriptDirPath, "tempScript.ps1"); + // var tempScriptDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + WriteObject(parsedScriptFileInfo); + WriteObject(updatedPSScriptFileContents); + var tempScriptFilePath = Path.Combine("./", "tempScript.ps1"); if (!Directory.Exists(tempScriptFilePath)) { Directory.CreateDirectory(tempScriptFilePath); @@ -319,7 +318,6 @@ protected override void ProcessRecord() if (PassThru) { WriteObject(parsedScriptFileInfo); - WriteObject(updatedScript); } } // else From 792cc12b8f7be82d975985103ffdfaea53bf49e4 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 16 Feb 2022 15:32:00 -0500 Subject: [PATCH 30/86] ensure update and test work and former validates as a separate step --- src/code/PSScriptFileInfo.cs | 10 +++++----- src/code/TestPSScriptFileInfo.cs | 5 +++-- src/code/UpdatePSScriptFileInfo.cs | 23 ++++++++++++++--------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 332fb7d91..0c04e85be 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -740,11 +740,11 @@ out ErrorRecord[] errors pSScriptFileString += "\n" + psHelpInfo; - GetEndOfFileContent(out string endOfFileAstContent); - if (!String.IsNullOrEmpty(endOfFileAstContent)) - { - pSScriptFileString += "\n" + endOfFileAstContent; - } + // GetEndOfFileContent(out string endOfFileAstContent); + // if (!String.IsNullOrEmpty(endOfFileAstContent)) + // { + // pSScriptFileString += "\n" + endOfFileAstContent; + // } fileContentsSuccessfullyCreated = true; return fileContentsSuccessfullyCreated; diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index a06334641..efc25f06c 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -73,12 +73,13 @@ protected override void ProcessRecord() { WriteWarning("The .ps1 script file passed in was not valid due to: " + error); } - // return false; + + WriteObject(false); } else { // WriteObject(parsedScriptInfo); - // return true; + WriteObject(true); } } diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 0d2ec89e8..66217fc28 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.IO; using System.Management.Automation; +using System.Text; using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets @@ -280,17 +281,21 @@ protected override void ProcessRecord() else { // write string of file contents to a temp file - // var tempScriptDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempScriptDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); WriteObject(parsedScriptFileInfo); WriteObject(updatedPSScriptFileContents); - var tempScriptFilePath = Path.Combine("./", "tempScript.ps1"); - if (!Directory.Exists(tempScriptFilePath)) + var tempScriptFilePath = Path.Combine(tempScriptDirPath, "tempScript.ps1"); + if (!Directory.Exists(tempScriptDirPath)) { - Directory.CreateDirectory(tempScriptFilePath); + Directory.CreateDirectory(tempScriptDirPath); + } + + using(FileStream fs = File.Create(tempScriptFilePath)) + { + byte[] info = new UTF8Encoding(true).GetBytes(updatedPSScriptFileContents); + fs.Write(info, 0, info.Length); } - File.Create(tempScriptFilePath); - File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); WriteObject(updatedPSScriptFileContents); if (Validate) @@ -309,12 +314,12 @@ protected override void ProcessRecord() } } - // write out updated script file's contents to original script file - // TODO: fix permissions to write to temp folder! - // File.WriteAllText(resolvedPath, updatedPSScriptFileContents); + File.Copy(tempScriptFilePath, resolvedFilePath, true); + Utils.DeleteDirectory(tempScriptDirPath); // File.Delete(tempScriptFilePath); // Utils.DeleteDirectory(tempScriptDirPath); + if (PassThru) { WriteObject(parsedScriptFileInfo); From dbe1878cdac684facaee6c334415c3c793f778a5 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 16 Feb 2022 16:37:37 -0500 Subject: [PATCH 31/86] fix for why Test would fail on an updated script --- src/code/PSScriptFileInfo.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 0c04e85be..afb7c3109 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -740,11 +740,11 @@ out ErrorRecord[] errors pSScriptFileString += "\n" + psHelpInfo; - // GetEndOfFileContent(out string endOfFileAstContent); - // if (!String.IsNullOrEmpty(endOfFileAstContent)) - // { - // pSScriptFileString += "\n" + endOfFileAstContent; - // } + GetEndOfFileContent(out string endOfFileAstContent); + if (!String.IsNullOrEmpty(endOfFileAstContent)) + { + pSScriptFileString += "\n" + endOfFileAstContent; + } fileContentsSuccessfullyCreated = true; return fileContentsSuccessfullyCreated; @@ -928,12 +928,14 @@ out string endOfFileContent endOfFileContent = String.Empty; if (ParamBlock != null) { + string paramBlockAttributes = String.Empty; if (ParamBlock.Attributes != null && ParamBlock.Attributes.Count > 0 && ParamBlock.Attributes[0].Extent != null) { - endOfFileContent += "\n" + ParamBlock.Attributes[0].Extent.Text; + paramBlockAttributes = ParamBlock.Attributes[0].Extent.Text; + endOfFileContent += "\n" + paramBlockAttributes; } - if (ParamBlock.Extent != null) + if (ParamBlock.Extent != null && !String.Equals(paramBlockAttributes, ParamBlock.Extent.Text, StringComparison.OrdinalIgnoreCase)) { endOfFileContent += "\n" + ParamBlock.Extent.Text; } From 7adeccd0088081c5574ba150a9d6375c3a5f0117 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 15:55:23 -0500 Subject: [PATCH 32/86] end of file contents are now sucessfully grabbed --- src/code/NewPSScriptFileInfo.cs | 3 +- src/code/PSScriptFileInfo.cs | 124 +++++------------------------ src/code/UpdatePSScriptFileInfo.cs | 15 ++-- 3 files changed, 31 insertions(+), 111 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index d34e575e2..245a8418b 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -254,6 +254,7 @@ protected override void ProcessRecord() description: Description); if (!currentScriptInfo.TryCreateScriptFileInfoString( + filePath: resolvedFilePath, pSScriptFileString: out string psScriptFileContents, errors: out ErrorRecord[] errors)) { @@ -271,7 +272,7 @@ protected override void ProcessRecord() if (PassThru) { - WriteObject(psScriptFileContents); + // WriteObject(psScriptFileContents); } } diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index afb7c3109..b72c9025c 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Management.Automation; using System.Management.Automation.Language; using System.Runtime.InteropServices; @@ -19,7 +20,7 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses /// This class contains information for a repository item. /// public sealed class PSScriptFileInfo - { + { #region Properties /// @@ -173,29 +174,8 @@ public sealed class PSScriptFileInfo [ValidateNotNullOrEmpty()] public string[] Functionality { get; set; } = new string[]{}; - /// - /// Optional end of file contents describing parameters - /// - public ParamBlockAst ParamBlock { get; set; } - - /// - /// Optional end of file contents describing begin block - /// - private NamedBlockAst BeginBlock { get; set; } - - /// - /// Optional end of file contents describing process block - /// - private NamedBlockAst ProcessBlock { get; set; } - - /// - /// Optional end of file contents describing end block - /// - private NamedBlockAst EndBlock { get; set; } - #endregion - #region Constructor private PSScriptFileInfo() {} public PSScriptFileInfo( @@ -280,7 +260,7 @@ public static bool TryParseScriptFileInfo( return successfullyParsed; } else if (ast != null) - { + { // Get the block/group comment beginning with <#PSScriptInfo Hashtable parsedPSScriptInfoHashtable = new Hashtable(); List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); @@ -392,38 +372,6 @@ public static bool TryParseScriptFileInfo( parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); } - // // see if there's any additional content in the file - // string optionalEndOfFileContents = String.Empty; - // if (ast.ParamBlock != null) - // { - - // if (ast.ParamBlock.Attributes != null && ast.ParamBlock.Attributes.Count > 0 && ast.ParamBlock.Attributes[0].Extent != null) - // { - // optionalEndOfFileContents += "\n" + ast.ParamBlock.Attributes[0].Extent.Text; - // } - - // if (ast.ParamBlock.Extent != null) - // { - // optionalEndOfFileContents += "\n" + ast.ParamBlock.Extent.Text; - // } - // } - - // if (ast.BeginBlock != null) - // { - // optionalEndOfFileContents += "\n" + ast.BeginBlock.Extent.Text; - // } - - // if (ast.ProcessBlock != null) - // { - // optionalEndOfFileContents += "\n" + ast.ProcessBlock.Extent.Text; - // } - - // if (ast.EndBlock != null) - // { - // optionalEndOfFileContents += "\n" + ast.EndBlock.Extent.Text; - // } - - string parsedVersion = (string) parsedPSScriptInfoHashtable["VERSION"]; string parsedAuthor = (string) parsedPSScriptInfoHashtable["AUTHOR"]; Guid parsedGuid = String.IsNullOrEmpty((string)parsedPSScriptInfoHashtable["GUID"]) ? Guid.NewGuid() : new Guid((string) parsedPSScriptInfoHashtable["GUID"]); @@ -482,27 +430,6 @@ public static bool TryParseScriptFileInfo( releaseNotes: parsedReleaseNotes, privateData: (string) parsedPSScriptInfoHashtable["PRIVATEDATA"], description: scriptCommentInfo.Description); - - // populate Ast block related properties in case the file has end of file content - if (ast.ParamBlock != null) - { - parsedScript.ParamBlock = ast.ParamBlock; - } - - if (ast.BeginBlock != null) - { - parsedScript.BeginBlock = ast.BeginBlock; - } - - if (ast.ProcessBlock != null) - { - parsedScript.ProcessBlock = ast.ProcessBlock; - } - - if (ast.EndBlock != null) - { - parsedScript.EndBlock = ast.EndBlock; - } } catch (Exception e) { @@ -533,6 +460,7 @@ public static bool TryParseScriptFileInfo( public static bool TryUpdateRequestedFields( ref PSScriptFileInfo originalScript, out string updatedPSScriptFileContents, + string filePath, out ErrorRecord[] errors, string version, Guid guid, @@ -666,6 +594,7 @@ public static bool TryUpdateRequestedFields( // create string contents for .ps1 file if (!updatedScript.TryCreateScriptFileInfoString( pSScriptFileString: out string psScriptFileContents, + filePath: filePath, errors: out ErrorRecord[] createFileContentErrors)) { errorsList.AddRange(createFileContentErrors); @@ -686,6 +615,7 @@ public static bool TryUpdateRequestedFields( /// Create .ps1 file contents with PSScriptFileInfo object's properties and output content as a string /// public bool TryCreateScriptFileInfoString( + string filePath, out string pSScriptFileString, out ErrorRecord[] errors ) @@ -740,7 +670,10 @@ out ErrorRecord[] errors pSScriptFileString += "\n" + psHelpInfo; - GetEndOfFileContent(out string endOfFileAstContent); + Console.WriteLine("made it here"); + GetEndOfFileLinesContent( + filePath: filePath, + endOfFileContent: out string endOfFileAstContent); if (!String.IsNullOrEmpty(endOfFileAstContent)) { pSScriptFileString += "\n" + endOfFileAstContent; @@ -921,39 +854,26 @@ out ErrorRecord error return psHelpInfoSuccessfullyCreated; } - public void GetEndOfFileContent( - out string endOfFileContent - ) + + public void GetEndOfFileLinesContent( + string filePath, + out string endOfFileContent) { endOfFileContent = String.Empty; - if (ParamBlock != null) - { - string paramBlockAttributes = String.Empty; - if (ParamBlock.Attributes != null && ParamBlock.Attributes.Count > 0 && ParamBlock.Attributes[0].Extent != null) - { - paramBlockAttributes = ParamBlock.Attributes[0].Extent.Text; - endOfFileContent += "\n" + paramBlockAttributes; - } - - if (ParamBlock.Extent != null && !String.Equals(paramBlockAttributes, ParamBlock.Extent.Text, StringComparison.OrdinalIgnoreCase)) - { - endOfFileContent += "\n" + ParamBlock.Extent.Text; - } - } - if (BeginBlock != null) + if (String.IsNullOrEmpty(filePath) || !filePath.EndsWith(".ps1")) { - endOfFileContent += "\n" + BeginBlock.Extent.Text; + return; } - if (ProcessBlock != null) - { - endOfFileContent += "\n" + ProcessBlock.Extent.Text; - } + string[] totalFileContents = File.ReadAllLines(filePath); + var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); + + var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); - if (EndBlock != null) + if (contentAfterDescription.Count() > 0) { - endOfFileContent += "\n" + EndBlock.Extent.Text; + endOfFileContent = String.Join("\n", contentAfterDescription); } } diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 66217fc28..0a114be63 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -1,3 +1,4 @@ +using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -250,12 +251,14 @@ protected override void ProcessRecord() } else { + Console.WriteLine("file content after test: " + File.ReadAllText(resolvedFilePath)); // update requested field if (!PSScriptFileInfo.TryUpdateRequestedFields( - ref parsedScriptFileInfo, - out string updatedPSScriptFileContents, - out ErrorRecord[] updateErrors, - version: Version, + originalScript: ref parsedScriptFileInfo, + updatedPSScriptFileContents: out string updatedPSScriptFileContents, + filePath: resolvedFilePath, + errors: out ErrorRecord[] updateErrors, + version: Version, guid: Guid, author: Author, companyName: CompanyName, @@ -282,8 +285,6 @@ protected override void ProcessRecord() { // write string of file contents to a temp file var tempScriptDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - WriteObject(parsedScriptFileInfo); - WriteObject(updatedPSScriptFileContents); var tempScriptFilePath = Path.Combine(tempScriptDirPath, "tempScript.ps1"); if (!Directory.Exists(tempScriptDirPath)) { @@ -296,8 +297,6 @@ protected override void ProcessRecord() fs.Write(info, 0, info.Length); } - WriteObject(updatedPSScriptFileContents); - if (Validate) { if (!PSScriptFileInfo.TryParseScriptFileInfo( From 92dfb7b6de578046cb65e8bd1e3369a2305de7fd Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 16:09:40 -0500 Subject: [PATCH 33/86] revert changes to InstallHelper; add that to separate PR --- src/code/InstallHelper.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index f9f3b0e94..8d75ae518 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -529,15 +529,7 @@ private List InstallPackage( continue; } } - else - { - if (!Utils.TryParseScriptFileInfo(scriptPath, _cmdletPassedIn, out Hashtable parsedPSScriptInfoHashtable)) - { - // Ran into errors parsing the module manifest file which was found in Utils.ParseModuleManifest() and written. - continue; - - } - } + // Delete the extra nupkg related files that are not needed and not part of the module/script DeleteExtraneousFiles(pkgIdentity, tempDirNameVersion); From 73e0fe7149bf0c7402349b0faf503ee3926860c5 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 16:33:18 -0500 Subject: [PATCH 34/86] change how we write to file to use FileStream --- src/code/NewPSScriptFileInfo.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 245a8418b..2d6d1b849 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -1,3 +1,4 @@ +using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -8,6 +9,7 @@ using System.Collections.Generic; using System.IO; using System.Management.Automation; +using System.Text; using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets @@ -264,15 +266,17 @@ protected override void ProcessRecord() } return; - // TODO: Anam, currently only one error and you return. So maybe this shouldn't be a list? - // But for extensability makes sense. } - File.WriteAllText(resolvedFilePath, psScriptFileContents); // TODO: Anam better way to do this? + using(FileStream fs = File.Create(resolvedFilePath)) + { + byte[] info = new UTF8Encoding(true).GetBytes(psScriptFileContents); + fs.Write(info, 0, info.Length); + } if (PassThru) { - // WriteObject(psScriptFileContents); + WriteObject(psScriptFileContents); } } From 932dfaef5c7f178cf696586cb86f302a41729c0d Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 16:38:38 -0500 Subject: [PATCH 35/86] clean up code in Test-PSScriptFileInfo --- src/code/TestPSScriptFileInfo.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index efc25f06c..904eef94e 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -64,23 +64,20 @@ protected override void ProcessRecord() var resolvedFilePath = resolvedPaths[0].Path; - if (!PSScriptFileInfo.TryParseScriptFileInfo( + bool isValidScript = PSScriptFileInfo.TryParseScriptFileInfo( scriptFileInfoPath: resolvedFilePath, parsedScript: out PSScriptFileInfo parsedScriptInfo, - errors: out ErrorRecord[] errors)) + errors: out ErrorRecord[] errors); + + if (!isValidScript) { foreach (ErrorRecord error in errors) { - WriteWarning("The .ps1 script file passed in was not valid due to: " + error); + WriteWarning("The .ps1 script file passed in was not valid due to: " + error.Exception.Message); } - - WriteObject(false); } - else - { - // WriteObject(parsedScriptInfo); - WriteObject(true); - } + + WriteObject(isValidScript); } #endregion From 8400d6f247d5fa4066899810f978fc4949beabad Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 16:43:38 -0500 Subject: [PATCH 36/86] use TryCreateUri instead of TryCreateUrl --- src/code/NewPSScriptFileInfo.cs | 6 ++-- src/code/UpdatePSScriptFileInfo.cs | 57 +++--------------------------- 2 files changed, 7 insertions(+), 56 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 2d6d1b849..d19851091 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -169,7 +169,7 @@ public sealed class NewPSScriptFileInfo : PSCmdlet protected override void ProcessRecord() { // validate Uri related parameters passed in as strings - if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUrl(uriString: ProjectUri, + if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri, cmdletPassedIn: this, uriResult: out _projectUri, errorRecord: out ErrorRecord projectErrorRecord)) @@ -177,7 +177,7 @@ protected override void ProcessRecord() ThrowTerminatingError(projectErrorRecord); } - if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUrl(uriString: LicenseUri, + if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUri(uriString: LicenseUri, cmdletPassedIn: this, uriResult: out _licenseUri, errorRecord: out ErrorRecord licenseErrorRecord)) @@ -185,7 +185,7 @@ protected override void ProcessRecord() ThrowTerminatingError(licenseErrorRecord); } - if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUrl(uriString: IconUri, + if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUri(uriString: IconUri, cmdletPassedIn: this, uriResult: out _iconUri, errorRecord: out ErrorRecord iconErrorRecord)) diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 0a114be63..e837b3f2f 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -175,7 +175,7 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet protected override void ProcessRecord() { // validate Uri related parameters passed in as strings - if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUrl(uriString: ProjectUri, + if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri, cmdletPassedIn: this, uriResult: out _projectUri, errorRecord: out ErrorRecord projectErrorRecord)) @@ -183,7 +183,7 @@ protected override void ProcessRecord() ThrowTerminatingError(projectErrorRecord); } - if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUrl(uriString: LicenseUri, + if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUri(uriString: LicenseUri, cmdletPassedIn: this, uriResult: out _licenseUri, errorRecord: out ErrorRecord licenseErrorRecord)) @@ -191,7 +191,7 @@ protected override void ProcessRecord() ThrowTerminatingError(licenseErrorRecord); } - if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUrl(uriString: IconUri, + if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUri(uriString: IconUri, cmdletPassedIn: this, uriResult: out _iconUri, errorRecord: out ErrorRecord iconErrorRecord)) @@ -251,8 +251,6 @@ protected override void ProcessRecord() } else { - Console.WriteLine("file content after test: " + File.ReadAllText(resolvedFilePath)); - // update requested field if (!PSScriptFileInfo.TryUpdateRequestedFields( originalScript: ref parsedScriptFileInfo, updatedPSScriptFileContents: out string updatedPSScriptFileContents, @@ -309,66 +307,19 @@ protected override void ProcessRecord() { WriteError(error); } + return; // TODO: Anam do we need this } } File.Copy(tempScriptFilePath, resolvedFilePath, true); Utils.DeleteDirectory(tempScriptDirPath); - // File.Delete(tempScriptFilePath); - // Utils.DeleteDirectory(tempScriptDirPath); - if (PassThru) { WriteObject(parsedScriptFileInfo); } } - // else - // { - // WriteVerbose("scriptFileContents: \n" + updatedPSScriptFileContents); // TODO: Anam remove - - // // write string of file contents to a temp file - // var tempScriptDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - // var tempScriptFilePath = Path.Combine(tempScriptDirPath, "tempScript.ps1"); - // if (!Directory.Exists(tempScriptFilePath)) - // { - // Directory.CreateDirectory(tempScriptFilePath); - // } - - // File.Create(tempScriptFilePath); - // File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); - // WriteObject(updatedPSScriptFileContents); // TODO: Anam remove - - // // if Validate, additionally validate this file's contents - // // if testing fails write errors and return-ish - // // if testing passes continue to copy temp file contents to permenant file and check for passthru - // if (!PSScriptFileInfo.TryParseScriptFileInfo( - // scriptFileInfoPath: tempScriptFilePath, - // out PSScriptFileInfo updatedPSScriptInfo, - // out ErrorRecord[] testErrors)) - // { - // WriteWarning("The updated test file created is invalid due to the following error(s):"); - // foreach (ErrorRecord error in testErrors) - // { - // WriteError(error); - // } - // } - // else - // { - // // write out updated script file's contents to original script file - // // TODO: fix permissions to write to temp folder! - // // File.WriteAllText(resolvedPath, updatedPSScriptFileContents); - // // File.Delete(tempScriptFilePath); - // // Utils.DeleteDirectory(tempScriptDirPath); - - // if (PassThru) - // { - // WriteObject(updatedPSScriptInfo); - // } - // } - - // } } } From 0d9c74aabed1beaf1333505ba70b815b15ff1d77 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 16:53:10 -0500 Subject: [PATCH 37/86] rename PSScriptFileInfo static methods --- src/code/PSScriptFileInfo.cs | 18 ++++++------------ src/code/TestPSScriptFileInfo.cs | 2 +- src/code/UpdatePSScriptFileInfo.cs | 6 +++--- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index b72c9025c..42bf047e8 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -1,4 +1,3 @@ -using System.Runtime.Serialization; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -70,7 +69,7 @@ public sealed class PSScriptFileInfo /// /// The list of modules required by the script - /// Hashtable keys: GUID, MaxVersion, Name (Required), RequiredVersion, Version + /// Hashtable keys: GUID, MaxVersion, ModuleName (Required), RequiredVersion, Version /// [Parameter] [ValidateNotNullOrEmpty()] @@ -110,7 +109,6 @@ public sealed class PSScriptFileInfo [ValidateNotNullOrEmpty()] public string Description { get; set; } - // TODO: seem to be optional keys for help comment, where would they be passed in from? constructor/New-X cmdlet? /// /// The synopsis of the script /// @@ -227,7 +225,7 @@ string description /// /// Tests the contents of the .ps1 file at the provided path /// - public static bool TryParseScriptFileInfo( + public static bool TryParseScriptFile( string scriptFileInfoPath, out PSScriptFileInfo parsedScript, out ErrorRecord[] errors) @@ -254,7 +252,9 @@ public static bool TryParseScriptFileInfo( var message = String.Format("Could not parse '{0}' as a PowerShell script file due to {1}.", scriptFileInfoPath, err.Message); var ex = new ArgumentException(message); var psScriptFileParseError = new ErrorRecord(ex, err.ErrorId, ErrorCategory.ParserError, null); - errorsList.Add(psScriptFileParseError); + errorsList.Add(psScriptFileParseError); + errors = errorsList.ToArray(); + } return successfullyParsed; @@ -340,13 +340,7 @@ public static bool TryParseScriptFileInfo( if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) { - // ReadOnlyCollection parsedRequiredModules = parsedScriptRequirements.RequiredModules; parsedModules = parsedScriptRequirements.RequiredModules; - // TODO: Anam was there any importance for this? - // if (parsedPSScriptInfoHashtable.ContainsKey("RequiredModules")) - // { - // parsedModules = (ReadOnlyCollection) parsedPSScriptInfoHashtable["RequiredModules"]; - // } parsedPSScriptInfoHashtable.Add("RequiredModules", parsedModules); } @@ -457,7 +451,7 @@ public static bool TryParseScriptFileInfo( /// 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 /// - public static bool TryUpdateRequestedFields( + public static bool TryUpdateScriptFile( ref PSScriptFileInfo originalScript, out string updatedPSScriptFileContents, string filePath, diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 904eef94e..50c769385 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -64,7 +64,7 @@ protected override void ProcessRecord() var resolvedFilePath = resolvedPaths[0].Path; - bool isValidScript = PSScriptFileInfo.TryParseScriptFileInfo( + bool isValidScript = PSScriptFileInfo.TryParseScriptFile( scriptFileInfoPath: resolvedFilePath, parsedScript: out PSScriptFileInfo parsedScriptInfo, errors: out ErrorRecord[] errors); diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index e837b3f2f..777c1a968 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -236,7 +236,7 @@ protected override void ProcessRecord() } // get PSScriptFileInfo object for current script contents - if (!PSScriptFileInfo.TryParseScriptFileInfo( + if (!PSScriptFileInfo.TryParseScriptFile( scriptFileInfoPath: resolvedFilePath, out PSScriptFileInfo parsedScriptFileInfo, out ErrorRecord[] errors)) @@ -251,7 +251,7 @@ protected override void ProcessRecord() } else { - if (!PSScriptFileInfo.TryUpdateRequestedFields( + if (!PSScriptFileInfo.TryUpdateScriptFile( originalScript: ref parsedScriptFileInfo, updatedPSScriptFileContents: out string updatedPSScriptFileContents, filePath: resolvedFilePath, @@ -297,7 +297,7 @@ protected override void ProcessRecord() if (Validate) { - if (!PSScriptFileInfo.TryParseScriptFileInfo( + if (!PSScriptFileInfo.TryParseScriptFile( scriptFileInfoPath: tempScriptFilePath, out parsedScriptFileInfo, out ErrorRecord[] testErrors)) From 0fddba495042c799bc1c930b841166153f8491c5 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 16:55:32 -0500 Subject: [PATCH 38/86] for Update-PSScriptFileInfo's errors make it clear which errors come from update and which from Validate --- src/code/UpdatePSScriptFileInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 777c1a968..5321554b4 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -273,7 +273,7 @@ protected override void ProcessRecord() privateData: PrivateData, description: Description)) { - WriteWarning("Could not update the specified file due to the following error(s):"); + WriteWarning("Updating the specified script file failed due to the following error(s):"); foreach (ErrorRecord error in updateErrors) { WriteError(error); @@ -302,7 +302,7 @@ protected override void ProcessRecord() out parsedScriptFileInfo, out ErrorRecord[] testErrors)) { - WriteWarning("The updated test file created is invalid due to the following error(s):"); + WriteWarning("Validating the updated script file failed due to the following error(s):"); foreach (ErrorRecord error in testErrors) { WriteError(error); From c5213dd73bc495fc2d01a912920d8d53f06d3799 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 16:59:48 -0500 Subject: [PATCH 39/86] clean up New-PSScriptFileInfo --- src/code/NewPSScriptFileInfo.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index d19851091..445659ecc 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -1,4 +1,3 @@ -using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -150,8 +149,7 @@ public sealed class NewPSScriptFileInfo : PSCmdlet public string PrivateData { get; set; } /// - /// If specified, passes the contents of the created .ps1 file to the console - /// If -Path is not specified, then .ps1 contents will just be written out for the user + /// If specified, the .ps1 file contents are additionally written out to the console /// [Parameter] public SwitchParameter PassThru { get; set; } From 9b131af712242f33d5c1fbd092e55c022c163873 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 17:05:46 -0500 Subject: [PATCH 40/86] remove unneccessary changes from Utils --- src/code/UpdatePSScriptFileInfo.cs | 1 - src/code/Utils.cs | 155 ----------------------------- 2 files changed, 156 deletions(-) diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 5321554b4..565dab972 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -1,4 +1,3 @@ -using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 0c938af01..fe46158bf 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -1,4 +1,3 @@ -using System.Runtime.CompilerServices; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -791,160 +790,6 @@ public static bool TryParseModuleManifest( return successfullyParsed; } - public static bool TryParseScriptFileInfo( - string scriptFileInfo, - PSCmdlet cmdletPassedIn, - out Hashtable parsedPSScriptInfoHashtable) - { - parsedPSScriptInfoHashtable = new Hashtable(); - bool successfullyParsed = false; - - if (scriptFileInfo.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) - { - // Parse the script file - var ast = Parser.ParseFile( - scriptFileInfo, - out Token[] tokens, - out ParseError[] errors); - - if (errors.Length > 0 && !String.Equals(errors[0].ErrorId, "WorkflowNotSupportedInPowerShellCore", StringComparison.OrdinalIgnoreCase)) - { - var message = String.Format("Could not parse '{0}' as a PowerShell script file.", scriptFileInfo); - var ex = new ArgumentException(message); - var psScriptFileParseError = new ErrorRecord(ex, "psScriptFileParseError", ErrorCategory.ParserError, null); - cmdletPassedIn.WriteError(psScriptFileParseError); - return successfullyParsed; - } - else if (ast != null) - { - // TODO: Anam do we still need to check if ast is not null? - // Get the block/group comment beginning with <#PSScriptInfo - List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); - string commentPattern = "<#PSScriptInfo"; - Regex rg = new Regex(commentPattern); - List psScriptInfoCommentTokens = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).ToList(); - - if (psScriptInfoCommentTokens.Count() == 0 || psScriptInfoCommentTokens[0] == null) - { - // TODO: Anam change to error from V2 - var message = String.Format("PSScriptInfo comment was missing or could not be parsed"); - var ex = new ArgumentException(message); - var psCommentParseError = new ErrorRecord(ex, "psScriptInfoCommentParseError", ErrorCategory.ParserError, null); - cmdletPassedIn.WriteError(psCommentParseError); - return successfullyParsed; - } - - string[] commentLines = Regex.Split(psScriptInfoCommentTokens[0].Text, "[\r\n]"); - // TODO: Anam clean above up as we don't need to store empty lines for CF and newline, I think? - string keyName = String.Empty; - string value = 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 - #> - */ - - /** - If comment line count is not more than two, it doesn't have the any metadata property - comment block would look like: - - <#PSScriptInfo - #> - */ - cmdletPassedIn.WriteVerbose("total comment lines: " + commentLines.Count()); - if (commentLines.Count() > 2) - { - // TODO: Anam is it an error if the metadata property is empty? - for (int i = 1; i < commentLines.Count(); i++) - { - string line = commentLines[i]; - cmdletPassedIn.WriteVerbose("i: " + i + "line: " + line); - if (String.IsNullOrEmpty(line)) - { - continue; - } - // A line is starting with . conveys a new metadata property - // __NEWLINE__ is used for replacing the value lines while adding the value to $PSScriptInfo object - if (line.Trim().StartsWith(".")) - { - // string partPattern = "[.\s+]"; - string[] parts = line.Trim().TrimStart('.').Split(); - keyName = parts[0]; - value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; - parsedPSScriptInfoHashtable.Add(keyName, value); - } - } - successfullyParsed = true; - } - - // get .DESCRIPTION comment - CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); - if (scriptCommentInfo != null && !String.IsNullOrEmpty(scriptCommentInfo.Description)) - { - parsedPSScriptInfoHashtable.Add("DESCRIPTION", scriptCommentInfo.Description); - } - - // get RequiredModules - ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; - if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) - { - ReadOnlyCollection parsedRequiredModules = parsedScriptRequirements.RequiredModules; - parsedPSScriptInfoHashtable.Add("RequiredModules", parsedRequiredModules); - } - - // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow - // TODO: Anam DefinedWorkflow is no longer supported as of PowerShellCore 6+, do we ignore error or reject script? - // List parsedFunctionAst = ast.FindAll(a => a.GetType().Name == "FunctionDefinitionAst", true).ToList(); - // foreach (var p in parsedFunctionAst) - // { - // Console.WriteLine(p.Parent.Extent.Text); - // p. - // } - var parsedFunctionAst = ast.FindAll(a => a is FunctionDefinitionAst, true); - List allCommands = new List(); - if (allCommands.Count() > 0) - { - foreach (var function in parsedFunctionAst) - { - allCommands.Add((FunctionDefinitionAst) function); - } - - List allCommandNames = allCommands.Select(a => a.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedCommands", allCommandNames); - - // b.Body.Extent.Text to get actual content. So "Function b.Name c.Body.Extent.Text" is that whole line lol - List allFunctionNames = allCommands.Where(a => !a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedFunctions", allFunctionNames); - - List allWorkflowNames = allCommands.Where(a => a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); - } - } - } - - return successfullyParsed; - } - #endregion #region Misc methods From 200f7b07c1501a3dba7b7c766033c36e99453969 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 17:07:16 -0500 Subject: [PATCH 41/86] Revert change to install test --- test/InstallPSResource.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index d23275536..bf73e0fb2 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -382,7 +382,7 @@ Describe 'Test Install-PSResource for Module' { } It "Install modules using -RequiredResourceFile with JSON file" { - $rrFileJSON = Join-Path -Path $psscriptroot -ChildPath "test" -AdditionalChildPath $RequiredResourceJSONFileName + $rrFileJSON = ".\$RequiredResourceJSONFileName" Install-PSResource -RequiredResourceFile $rrFileJSON -Verbose From 4aff970b22e4b83c6fab1b2e4f1a1e1f06eed8b2 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 17:23:23 -0500 Subject: [PATCH 42/86] add validation on Description property to not contain <# or #> --- src/code/PSScriptFileInfo.cs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 42bf047e8..eb8950b71 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -319,16 +319,16 @@ public static bool TryParseScriptFile( CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); if (scriptCommentInfo != null) { - if (!String.IsNullOrEmpty(scriptCommentInfo.Description)) + if (!String.IsNullOrEmpty(scriptCommentInfo.Description) && !scriptCommentInfo.Description.Contains("<#") && !scriptCommentInfo.Description.Contains("#>")) { parsedPSScriptInfoHashtable.Add("DESCRIPTION", scriptCommentInfo.Description); } else { - var message = String.Format("PSScript is missing the required Description property"); + 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 psScriptMissingDescriptionPropertyError = new ErrorRecord(ex, "psScriptDescriptionMissingDescription", ErrorCategory.ParserError, null); - errorsList.Add(psScriptMissingDescriptionPropertyError); + var psScriptMissingDescriptionOrInvalidPropertyError = new ErrorRecord(ex, "psScriptDescriptionMissingOrInvalidDescription", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingDescriptionOrInvalidPropertyError); successfullyParsed = false; return successfullyParsed; } @@ -474,7 +474,6 @@ public static bool TryUpdateScriptFile( string description) { bool successfullyUpdated = false; - // updatedScript = null; updatedPSScriptFileContents = String.Empty; errors = new ErrorRecord[]{}; List errorsList = new List(); @@ -794,6 +793,15 @@ out ErrorRecord error 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)); @@ -871,6 +879,14 @@ public void GetEndOfFileLinesContent( } } + /// + /// Ensure no fields (passed as stringToValidate) contains '<#' or '#>' (would break comment section) + /// + public bool stringContainsComment(string stringToValidate) + { + return stringToValidate.Contains("<#") || stringToValidate.Contains("#>"); + } + #endregion } } \ No newline at end of file From 3d8eb1032d9faea46cfef03855602cf34e337368 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 17:24:06 -0500 Subject: [PATCH 43/86] fix typo --- src/code/PSScriptFileInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index eb8950b71..bf74e2af1 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -793,7 +793,7 @@ out ErrorRecord error return psHelpInfoSuccessfullyCreated; } - if (stringContainsComment(Description)) + 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); @@ -882,7 +882,7 @@ public void GetEndOfFileLinesContent( /// /// Ensure no fields (passed as stringToValidate) contains '<#' or '#>' (would break comment section) /// - public bool stringContainsComment(string stringToValidate) + public bool StringContainsComment(string stringToValidate) { return stringToValidate.Contains("<#") || stringToValidate.Contains("#>"); } From 3a0eac64d49c6ddda94f7fe43c0975a5ad47ef77 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 17 Feb 2022 18:02:54 -0500 Subject: [PATCH 44/86] refactor CreateModuleSpecification() to have out variable of type ModuleSpecication array not list --- src/code/NewPSScriptFileInfo.cs | 5 ++--- src/code/PSScriptFileInfo.cs | 1 - src/code/UpdatePSScriptFileInfo.cs | 7 +++---- src/code/Utils.cs | 10 ++++------ 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 445659ecc..d83c3f0c9 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -218,10 +218,9 @@ protected override void ProcessRecord() var resolvedFilePath = resolvedPaths[0].Path; - List validatedRequiredModuleSpecifications = new List(); + ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; if (RequiredModules != null && RequiredModules.Length > 0) { - // TODO: ANAM have this return array not list for mod specs Utils.CreateModuleSpecification( moduleSpecHashtables: RequiredModules, out validatedRequiredModuleSpecifications, @@ -245,7 +244,7 @@ protected override void ProcessRecord() licenseUri: _licenseUri, projectUri: _projectUri, iconUri: _iconUri, - requiredModules: validatedRequiredModuleSpecifications.ToArray(), + requiredModules: validatedRequiredModuleSpecifications, externalModuleDependencies: ExternalModuleDependencies, requiredScripts: RequiredScripts, externalScriptDependencies: ExternalScriptDependencies, diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index bf74e2af1..5b07d7d7f 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -663,7 +663,6 @@ out ErrorRecord[] errors pSScriptFileString += "\n" + psHelpInfo; - Console.WriteLine("made it here"); GetEndOfFileLinesContent( filePath: filePath, endOfFileContent: out string endOfFileAstContent); diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 565dab972..dece3e3b8 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -217,10 +217,9 @@ protected override void ProcessRecord() string resolvedFilePath = resolvedPaths[0].Path; - List validatedRequiredModuleSpecifications = new List(); + ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; if (RequiredModules != null && RequiredModules.Length > 0) { - // TODO: ANAM have this return array not list for mod specs Utils.CreateModuleSpecification( moduleSpecHashtables: RequiredModules, out validatedRequiredModuleSpecifications, @@ -264,7 +263,7 @@ protected override void ProcessRecord() licenseUri: _licenseUri, projectUri: _projectUri, iconUri: _iconUri, - requiredModules: validatedRequiredModuleSpecifications.ToArray(), + requiredModules: validatedRequiredModuleSpecifications, externalModuleDependencies: ExternalModuleDependencies, requiredScripts: RequiredScripts, externalScriptDependencies: ExternalScriptDependencies, @@ -307,7 +306,7 @@ protected override void ProcessRecord() WriteError(error); } - return; // TODO: Anam do we need this + return; // TODO: if Validation was requested and fails, do we still write out updated file PSScriptFileInfo object? } } diff --git a/src/code/Utils.cs b/src/code/Utils.cs index fe46158bf..2c83a13de 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -831,11 +831,11 @@ public static Hashtable ConvertJsonToHashtable( public static void CreateModuleSpecification( Hashtable[] moduleSpecHashtables, - out List validatedModuleSpecs, + out ModuleSpecification[] validatedModuleSpecs, out ErrorRecord[] errors) { - // bool successfullyCreatedModuleSpecs = false; List errorList = new List(); + validatedModuleSpecs = new ModuleSpecification[]{}; List moduleSpecsList = new List(); foreach(Hashtable moduleSpec in moduleSpecHashtables) @@ -851,7 +851,7 @@ public static void CreateModuleSpecification( // at this point it must contain ModuleName key. string moduleSpecName = (string) moduleSpec["ModuleName"]; - ModuleSpecification currentModuleSpec = null; + ModuleSpecification currentModuleSpec; if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) { // pass to ModuleSpecification(string) constructor @@ -870,7 +870,6 @@ public static void CreateModuleSpecification( } else { - // TODO: ANAM perhaps not else string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; string moduleSpecModuleVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["ModuleVersion"] : String.Empty; string moduleSpecRequiredVersion = moduleSpec.ContainsKey("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; @@ -915,8 +914,7 @@ public static void CreateModuleSpecification( } errors = errorList.ToArray(); - validatedModuleSpecs = moduleSpecsList; - // return successfullyCreatedModuleSpecs; + validatedModuleSpecs = moduleSpecsList.ToArray(); } #endregion From ac696acffc6c79203b83c43bc307e666e0ddce49 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 16 Mar 2022 14:00:37 -0400 Subject: [PATCH 45/86] some changes to script file info cmdlets --- src/PowerShellGet.psd1 | 1 + src/code/NewPSScriptFileInfo.cs | 5 +++-- test/NewPSScriptFileInfo.Tests.ps1 | 32 ++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 test/NewPSScriptFileInfo.Tests.ps1 diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index 8e9717f81..fbd9cda82 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -24,6 +24,7 @@ 'New-PSScriptFileInfo', 'Publish-PSResource', 'Test-PSScriptFileInfo', + 'Test-VersionEquality', 'Uninstall-PSResource', 'Unregister-PSResourceRepository', 'Update-PSResource', diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index d83c3f0c9..1a5bdd908 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -198,7 +198,8 @@ protected override void ProcessRecord() var InvalidPathError = new ErrorRecord(ex, "InvalidPath", ErrorCategory.InvalidArgument, null); ThrowTerminatingError(InvalidPathError); } - else if (File.Exists(FilePath) && !Force) + + if (File.Exists(FilePath) && !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."; @@ -279,4 +280,4 @@ protected override void ProcessRecord() #endregion } -} +} \ No newline at end of file diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 new file mode 100644 index 000000000..281b51067 --- /dev/null +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force + +Describe "Test New-PSScriptFileInfo" { + BeforeEach { + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" + $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" + $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) + Get-NewTestDirs($tmpDirPaths) + } + AfterEach { + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" + $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" + $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) + Get-RemoveTestDirs($tmpDirPaths) + } + + It "create .ps1 file with minimal required fields" { + $pathTestRes = Test-Path $tmpDir1Path + $pathTestRes | Should -Be $true + Write-Host $pathTestRes + $basicScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "basicTestScript.ps1" + Write-Host $basicScriptFilePath + $scriptDescription = "this is a test script" + $res = New-PSScriptFileInfo -FilePath $basicScriptFilePath -Description $scriptDescription -PassThru + $res.Description | Should -Be $scriptDescription + } +} From d49fba4ded751255778853e3b7863db9dae66c65 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 30 Mar 2022 11:19:27 -0400 Subject: [PATCH 46/86] comment out buggy code --- src/code/PSScriptFileInfo.cs | 62 +++++++++++++++++++++--------- test/NewPSScriptFileInfo.Tests.ps1 | 4 +- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 5b07d7d7f..348cacae2 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -609,7 +609,7 @@ public static bool TryUpdateScriptFile( /// public bool TryCreateScriptFileInfoString( string filePath, - out string pSScriptFileString, + out string pSScriptFileString, // this is the string with the contents we want to put in the new ps1 file out ErrorRecord[] errors ) { @@ -663,13 +663,18 @@ out ErrorRecord[] errors pSScriptFileString += "\n" + psHelpInfo; - GetEndOfFileLinesContent( - filePath: filePath, - endOfFileContent: out string endOfFileAstContent); - if (!String.IsNullOrEmpty(endOfFileAstContent)) - { - pSScriptFileString += "\n" + endOfFileAstContent; - } + // GetEndOfFileLinesContent2( + + // ) + + + // GetEndOfFileLinesContent( + // filePath: filePath, + // endOfFileContent: out string endOfFileAstContent); + // if (!String.IsNullOrEmpty(endOfFileAstContent)) + // { + // pSScriptFileString += "\n" + endOfFileAstContent; + // } fileContentsSuccessfullyCreated = true; return fileContentsSuccessfullyCreated; @@ -856,26 +861,45 @@ out ErrorRecord error } + // public void GetEndOfFileLinesContent2( + // string totalFileContents2, + // out string endOfFileContent) + // { + // // if (String.IsNullOrEmpty(filePath) || !filePath.EndsWith(".ps1")) + // // { + // // return; + // // } + + // // string[] totalFileContents = File.ReadAllLines(filePath); + // // var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); + + // // var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); + + // // if (contentAfterDescription.Count() > 0) + // // { + // // endOfFileContent = String.Join("\n", contentAfterDescription); + // // } + // } public void GetEndOfFileLinesContent( string filePath, out string endOfFileContent) { endOfFileContent = String.Empty; - if (String.IsNullOrEmpty(filePath) || !filePath.EndsWith(".ps1")) - { - return; - } + // if (String.IsNullOrEmpty(filePath) || !filePath.EndsWith(".ps1")) + // { + // return; + // } - string[] totalFileContents = File.ReadAllLines(filePath); - var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); + // string[] totalFileContents = File.ReadAllLines(filePath); + // var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); - var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); + // var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); - if (contentAfterDescription.Count() > 0) - { - endOfFileContent = String.Join("\n", contentAfterDescription); - } + // if (contentAfterDescription.Count() > 0) + // { + // endOfFileContent = String.Join("\n", contentAfterDescription); + // } } /// diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index 281b51067..2399efeaf 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -26,7 +26,7 @@ Describe "Test New-PSScriptFileInfo" { $basicScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "basicTestScript.ps1" Write-Host $basicScriptFilePath $scriptDescription = "this is a test script" - $res = New-PSScriptFileInfo -FilePath $basicScriptFilePath -Description $scriptDescription -PassThru - $res.Description | Should -Be $scriptDescription + # $res = New-PSScriptFileInfo -FilePath $basicScriptFilePath -Description $scriptDescription -PassThru + # $res.Description | Should -Be $scriptDescription } } From 20f3c3c51007ada8b72ce8b32f82e69930ca7db6 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 31 Mar 2022 13:52:28 -0400 Subject: [PATCH 47/86] fix bug in .ps1 file creation that didn't recognize description as valid and handles relative paths now --- src/code/NewPSScriptFileInfo.cs | 22 ++++++------ src/code/PSScriptFileInfo.cs | 52 ++++++++++++++++++++++++++--- src/code/TestPSScriptFileInfo.cs | 24 +++++++++---- src/code/UpdatePSScriptFileInfo.cs | 15 +++++++-- test/NewPSScriptFileInfo.Tests.ps1 | 16 ++++----- test/TestPSScriptFileInfo.Tests.ps1 | 32 ++++++++++++++++++ 6 files changed, 128 insertions(+), 33 deletions(-) create mode 100644 test/TestPSScriptFileInfo.Tests.ps1 diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 1a5bdd908..88a14bb61 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -208,16 +208,16 @@ protected override void ProcessRecord() ThrowTerminatingError(ScriptAtPathAlreadyExistsError); } - 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 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; + // var resolvedFilePath = resolvedPaths[0].Path; ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; if (RequiredModules != null && RequiredModules.Length > 0) @@ -254,7 +254,7 @@ protected override void ProcessRecord() description: Description); if (!currentScriptInfo.TryCreateScriptFileInfoString( - filePath: resolvedFilePath, + // filePath: resolvedFilePath, pSScriptFileString: out string psScriptFileContents, errors: out ErrorRecord[] errors)) { @@ -266,7 +266,7 @@ protected override void ProcessRecord() return; } - using(FileStream fs = File.Create(resolvedFilePath)) + using(FileStream fs = File.Create(FilePath)) { byte[] info = new UTF8Encoding(true).GetBytes(psScriptFileContents); fs.Write(info, 0, info.Length); diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 348cacae2..b80e9c75b 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -20,6 +20,10 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses /// public sealed class PSScriptFileInfo { + #region Members + private string[] fileContents = new string[]{}; + #endregion + #region Properties /// @@ -220,6 +224,25 @@ string description #endregion + #region Public Methods + public string[] GetFileContents() + { + if (fileContents == null || fileContents.Length == 0) + { + fileContents = Utils.EmptyStrArray; + } + + return fileContents; + } + + public void SetFileContents(string[] newFileContents) + { + // TODO: new file contents is a misleading name...rename + fileContents = newFileContents; + } + + #endregion + #region Public Static Methods /// @@ -424,6 +447,17 @@ public static bool TryParseScriptFile( releaseNotes: parsedReleaseNotes, privateData: (string) parsedPSScriptInfoHashtable["PRIVATEDATA"], description: scriptCommentInfo.Description); + + // TODO: get file contents and set member of PSScriptFileInfo instance + // Or should I get end of file contents just right here? + string[] totalFileContents = File.ReadAllLines(scriptFileInfoPath); + var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); + var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); + if (contentAfterDescription.Count() > 0) + { + parsedScript.SetFileContents(contentAfterDescription.ToArray()); + } + } catch (Exception e) { @@ -450,11 +484,12 @@ public static bool TryParseScriptFile( /// /// 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 + /// TODO: should this really be static? I think not, but also we create a new instance for the updated PSScriptFileInfo so as to not data loss /// public static bool TryUpdateScriptFile( ref PSScriptFileInfo originalScript, out string updatedPSScriptFileContents, - string filePath, + // string filePath, out ErrorRecord[] errors, string version, Guid guid, @@ -587,7 +622,7 @@ public static bool TryUpdateScriptFile( // create string contents for .ps1 file if (!updatedScript.TryCreateScriptFileInfoString( pSScriptFileString: out string psScriptFileContents, - filePath: filePath, + // filePath: filePath, errors: out ErrorRecord[] createFileContentErrors)) { errorsList.AddRange(createFileContentErrors); @@ -595,6 +630,12 @@ public static bool TryUpdateScriptFile( return successfullyUpdated; } + // TODO: get end of file contents here + if (updatedScript.GetFileContents() != null && updatedScript.GetFileContents().Length > 0) + { + psScriptFileContents += "\n" + String.Join("\n", updatedScript.GetFileContents()); + } + successfullyUpdated = true; updatedPSScriptFileContents = psScriptFileContents; return successfullyUpdated; @@ -605,10 +646,11 @@ public static bool TryUpdateScriptFile( #region Public Methods /// - /// Create .ps1 file contents with PSScriptFileInfo object's properties and output content as a string + /// 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 /// public bool TryCreateScriptFileInfoString( - string filePath, + // string filePath, out string pSScriptFileString, // this is the string with the contents we want to put in the new ps1 file out ErrorRecord[] errors ) @@ -661,6 +703,8 @@ out ErrorRecord[] errors 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; // GetEndOfFileLinesContent2( diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 50c769385..98f7bdffe 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -1,3 +1,4 @@ +using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -37,13 +38,13 @@ public sealed class TestPSScriptFileInfo : PSCmdlet protected override void ProcessRecord() { - if (!File.Exists(FilePath)) - { - 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); - } + // if (!File.Exists(FilePath)) + // { + // 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); + // } if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { @@ -53,6 +54,7 @@ protected override void ProcessRecord() ThrowTerminatingError(InvalidPathError); } + // var resolvedPath = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath).First().Path; var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); if (resolvedPaths.Count != 1) { @@ -64,6 +66,14 @@ protected override void ProcessRecord() 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.TryParseScriptFile( scriptFileInfoPath: resolvedFilePath, parsedScript: out PSScriptFileInfo parsedScriptInfo, diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index dece3e3b8..445a1a0a5 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -198,9 +198,10 @@ protected override void ProcessRecord() ThrowTerminatingError(iconErrorRecord); } - if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) || !File.Exists(FilePath)) + // TODO: must resolve relative paths before checking if path exists + if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { - var exMessage = "Path needs to exist and end with a .ps1 file. Example: C:/Users/john/x/MyScript.ps1"; + 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); @@ -216,6 +217,14 @@ protected override void ProcessRecord() } string 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); + } ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; if (RequiredModules != null && RequiredModules.Length > 0) @@ -252,7 +261,7 @@ protected override void ProcessRecord() if (!PSScriptFileInfo.TryUpdateScriptFile( originalScript: ref parsedScriptFileInfo, updatedPSScriptFileContents: out string updatedPSScriptFileContents, - filePath: resolvedFilePath, + // filePath: resolvedFilePath, errors: out ErrorRecord[] updateErrors, version: Version, guid: Guid, diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index 2399efeaf..8e7b501e1 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -19,14 +19,14 @@ Describe "Test New-PSScriptFileInfo" { Get-RemoveTestDirs($tmpDirPaths) } - It "create .ps1 file with minimal required fields" { - $pathTestRes = Test-Path $tmpDir1Path - $pathTestRes | Should -Be $true - Write-Host $pathTestRes - $basicScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "basicTestScript.ps1" - Write-Host $basicScriptFilePath + It "create .ps1 file with minimal required fields" { + $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testScript2.ps1" + Write-Host $scriptFilePath $scriptDescription = "this is a test script" - # $res = New-PSScriptFileInfo -FilePath $basicScriptFilePath -Description $scriptDescription -PassThru - # $res.Description | Should -Be $scriptDescription + $res = New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -PassThru + Write-host $res + $res | Should -Not -BeNullOrEmpty + + Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue } } diff --git a/test/TestPSScriptFileInfo.Tests.ps1 b/test/TestPSScriptFileInfo.Tests.ps1 new file mode 100644 index 000000000..2399efeaf --- /dev/null +++ b/test/TestPSScriptFileInfo.Tests.ps1 @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force + +Describe "Test New-PSScriptFileInfo" { + BeforeEach { + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" + $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" + $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) + Get-NewTestDirs($tmpDirPaths) + } + AfterEach { + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" + $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" + $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) + Get-RemoveTestDirs($tmpDirPaths) + } + + It "create .ps1 file with minimal required fields" { + $pathTestRes = Test-Path $tmpDir1Path + $pathTestRes | Should -Be $true + Write-Host $pathTestRes + $basicScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "basicTestScript.ps1" + Write-Host $basicScriptFilePath + $scriptDescription = "this is a test script" + # $res = New-PSScriptFileInfo -FilePath $basicScriptFilePath -Description $scriptDescription -PassThru + # $res.Description | Should -Be $scriptDescription + } +} From 74528fa8d2622bd0f38e78b94aacc1bd9510e9c3 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 31 Mar 2022 16:58:13 -0400 Subject: [PATCH 48/86] add tests for New-PSScriptFileInfo --- src/code/PSScriptFileInfo.cs | 1 + test/NewPSScriptFileInfo.Tests.ps1 | 36 +++++++++++++++++++++++++++++- test/PSGetTestUtils.psm1 | 2 +- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index b80e9c75b..86c4841a3 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -899,6 +899,7 @@ out ErrorRecord error psHelpInfoLines.Add(String.Format(".FUNCTIONALITY\n{0}", String.Join("\n", Functionality))); } + // TODO: add newline before #> or will that just inflate it with newlines each round? psHelpInfoLines.Add("#>"); psHelpInfo = String.Join("\n", psHelpInfoLines); return psHelpInfoSuccessfullyCreated; diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index 8e7b501e1..2547dfcf5 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -21,7 +21,6 @@ Describe "Test New-PSScriptFileInfo" { It "create .ps1 file with minimal required fields" { $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testScript2.ps1" - Write-Host $scriptFilePath $scriptDescription = "this is a test script" $res = New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -PassThru Write-host $res @@ -29,4 +28,39 @@ Describe "Test New-PSScriptFileInfo" { Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue } + + It "create .ps1 file with relative path" { + $relativeCurrentPath = Get-Location + $scriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "testScript2.ps1" + $scriptDescription = "this is a test script" + $res = New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -PassThru + Write-host $res + $res | Should -Not -BeNullOrEmpty + + Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue + Remove-Item -Path (Join-Path -Path $relativeCurrentPath -ChildPath "testScript2.ps1") + } + + It "create .ps1 file with RequiredModules" { + $hashtable1 = @{ModuleName = "ReqModule1"} + $hashtable2 = @{ModuleName = "ReqModule2"; ModuleVersion = "1.0.0.0"} + $requiredModulesHashtables = $hashtable1, $hashtable2 + + $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testScript2.ps1" + $scriptDescription = "this is a test script" + $res = New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -RequiredModules $requiredModulesHashtables -PassThru + Write-Host $res + $res | Should -Not -BeNullOrEmpty + + Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue + #MaximumVersion, #RequiredVersion, #ModuleVersion + # $reqModsActual = @(@{"ModuleName" = "ReqModule1"},@{"ModuleVersion"="1.0.0.0";"ModuleName"="ReqModule2"}) + } + + # test with required modules + # test with each param really....would be easier to test if we returned PSSriptFileInfoobject + + + + } diff --git a/test/PSGetTestUtils.psm1 b/test/PSGetTestUtils.psm1 index 8982e6af7..c7f6da688 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 } } } From 4f9810fa1db5d397deff3afacaa4162a35216ad3 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 28 Apr 2022 11:55:00 -0400 Subject: [PATCH 49/86] fix bug with RequiredMOdules ModuleSpecification(Hashtable) creation and error handling --- src/code/Utils.cs | 18 +++++++++++++++--- test/NewPSScriptFileInfo.Tests.ps1 | 8 +++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index df43b6175..87fa4875f 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -25,7 +25,7 @@ internal static class Utils { #region String fields - public static readonly string[] EmptyStrArray = Array.Empty(); + public static readonly string[] EmptyStrArray = Array.Empty(); public const string PSDataFileExt = ".psd1"; private const string ConvertJsonToHashtableScript = @" param ( @@ -840,6 +840,7 @@ public static void CreateModuleSpecification( 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 is missing a key 'ModuleName' and associated value, which is required for each module specification entry"; @@ -870,11 +871,22 @@ public static void CreateModuleSpecification( } else { - string moduleSpecMaxVersion = moduleSpec.ContainsKey("MaximumVersion") ? (string) moduleSpec["MaxiumumVersion"] : String.Empty; + // 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("ModuleVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; + string moduleSpecRequiredVersion = moduleSpec.ContainsKey("RequiredVersion") ? (string) moduleSpec["RequiredVersion"] : String.Empty; Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; // TODO: ANAM this can be the default + if (String.IsNullOrEmpty(moduleSpecMaxVersion) && String.IsNullOrEmpty(moduleSpecModuleVersion) && String.IsNullOrEmpty(moduleSpecRequiredVersion)) + { + var exMessage = String.Format("ModuleSpecification hashtable requires one of the following keys: MaximumVersion, ModuleVersion, RequiredVersion and failed to be created for {0}", moduleSpecName); + var ex = new ArgumentException(exMessage); + var MissingModuleSpecificationMemberError = new ErrorRecord(ex, "MissingModuleSpecificationMember", ErrorCategory.InvalidArgument, null); + errorList.Add(MissingModuleSpecificationMemberError); + + continue; + } + Hashtable moduleSpecHash = new Hashtable(); moduleSpecHash.Add("ModuleName", moduleSpecName); diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index 2547dfcf5..693c0ebe8 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -42,9 +42,11 @@ Describe "Test New-PSScriptFileInfo" { } It "create .ps1 file with RequiredModules" { - $hashtable1 = @{ModuleName = "ReqModule1"} - $hashtable2 = @{ModuleName = "ReqModule2"; ModuleVersion = "1.0.0.0"} - $requiredModulesHashtables = $hashtable1, $hashtable2 + $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"} + $requiredModulesHashtables = $hashtable1, $hashtable2, $hashtable3, $hashtable4 $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testScript2.ps1" $scriptDescription = "this is a test script" From deeaddba7507552d69b195f2679b3881b901dd9c Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 6 Jun 2022 11:33:52 -0400 Subject: [PATCH 50/86] add tests for Test-PSScriptFileInfo --- src/code/PSScriptFileInfo.cs | 87 ++++++++++++++++++++++------- src/code/TestPSScriptFileInfo.cs | 9 --- test/NewPSScriptFileInfo.Tests.ps1 | 9 ++- test/TestPSScriptFileInfo.Tests.ps1 | 71 +++++++++++++++++++++-- 4 files changed, 138 insertions(+), 38 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 86c4841a3..8ae78aebd 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -340,23 +340,32 @@ public static bool TryParseScriptFile( // get .DESCRIPTION comment CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); - if (scriptCommentInfo != null) + if (scriptCommentInfo == null) { - if (!String.IsNullOrEmpty(scriptCommentInfo.Description) && !scriptCommentInfo.Description.Contains("<#") && !scriptCommentInfo.Description.Contains("#>")) - { - parsedPSScriptInfoHashtable.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); - successfullyParsed = false; - return successfullyParsed; - } + 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("#>")) + { + parsedPSScriptInfoHashtable.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(); + successfullyParsed = false; + return successfullyParsed; + } + + // get RequiredModules ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; ReadOnlyCollection parsedModules = new List().AsReadOnly(); @@ -389,19 +398,55 @@ public static bool TryParseScriptFile( parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); } - string parsedVersion = (string) parsedPSScriptInfoHashtable["VERSION"]; - string parsedAuthor = (string) parsedPSScriptInfoHashtable["AUTHOR"]; - Guid parsedGuid = String.IsNullOrEmpty((string)parsedPSScriptInfoHashtable["GUID"]) ? Guid.NewGuid() : new Guid((string) parsedPSScriptInfoHashtable["GUID"]); - if (String.IsNullOrEmpty(parsedVersion) || String.IsNullOrEmpty(parsedAuthor) || parsedGuid == Guid.Empty) + if (!parsedPSScriptInfoHashtable.ContainsKey("VERSION") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["VERSION"])) { - var message = String.Format("PSScript file is missing one of the following required properties: Version, Author, Guid"); + var message = String.Format("PSScript file is missing the required Version property"); var ex = new ArgumentException(message); - var psScriptMissingRequiredPropertyError = new ErrorRecord(ex, "psScriptMissingRequiredProperty", ErrorCategory.ParserError, null); - errorsList.Add(psScriptMissingRequiredPropertyError); + var psScriptMissingVersionError = new ErrorRecord(ex, "psScriptMissingVersion", ErrorCategory.ParserError, null); + errorsList.Add(psScriptMissingVersionError); + errors = errorsList.ToArray(); + successfullyParsed = false; + return successfullyParsed; + } + + if (!parsedPSScriptInfoHashtable.ContainsKey("AUTHOR") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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); + errors = errorsList.ToArray(); successfullyParsed = false; return successfullyParsed; } + if (!parsedPSScriptInfoHashtable.ContainsKey("GUID") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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(); + successfullyParsed = false; + return successfullyParsed; + } + + + string parsedVersion = (string) parsedPSScriptInfoHashtable["VERSION"]; + string parsedAuthor = (string) parsedPSScriptInfoHashtable["AUTHOR"]; + Guid parsedGuid = new Guid((string) parsedPSScriptInfoHashtable["GUID"]); + + // if (String.IsNullOrEmpty(parsedVersion) || String.IsNullOrEmpty(parsedAuthor) || parsedGuid == Guid.Empty) + // { + // var message = String.Format("PSScript file is missing one of the following required properties: Version, Author, Guid"); + // var ex = new ArgumentException(message); + // var psScriptMissingRequiredPropertyError = new ErrorRecord(ex, "psScriptMissingRequiredProperty", ErrorCategory.ParserError, null); + // errorsList.Add(psScriptMissingRequiredPropertyError); + // errors = errorsList.ToArray(); + // successfullyParsed = false; + // return successfullyParsed; + // } + try { char[] spaceDelimeter = new char[]{' '}; diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 98f7bdffe..ec8f6fdf7 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -38,14 +38,6 @@ public sealed class TestPSScriptFileInfo : PSCmdlet protected override void ProcessRecord() { - // if (!File.Exists(FilePath)) - // { - // 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); - // } - if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { var exMessage = "Path needs to end with a .ps1 file. Example: C:/Users/john/x/MyScript.ps1"; @@ -54,7 +46,6 @@ protected override void ProcessRecord() ThrowTerminatingError(InvalidPathError); } - // var resolvedPath = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath).First().Path; var resolvedPaths = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); if (resolvedPaths.Count != 1) { diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index 693c0ebe8..c486663dc 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -42,6 +42,7 @@ Describe "Test New-PSScriptFileInfo" { } It "create .ps1 file with RequiredModules" { + #MaximumVersion, #RequiredVersion, #ModuleVersion $hashtable1 = @{ModuleName = "RequiredModule1"} $hashtable2 = @{ModuleName = "RequiredModule2"; ModuleVersion = "1.0.0.0"} $hashtable3 = @{ModuleName = "RequiredModule3"; RequiredVersion = "2.5.0.0"} @@ -55,12 +56,14 @@ Describe "Test New-PSScriptFileInfo" { $res | Should -Not -BeNullOrEmpty Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue - #MaximumVersion, #RequiredVersion, #ModuleVersion - # $reqModsActual = @(@{"ModuleName" = "ReqModule1"},@{"ModuleVersion"="1.0.0.0";"ModuleName"="ReqModule2"}) } - # test with required modules # test with each param really....would be easier to test if we returned PSSriptFileInfoobject + # FilePath, Version, Author, Description, Guid, CompanyName, Copyright, RequiredModules, + # ExternalModuleDependencies, RequiredScripts, ExternalScriptDependencies, Tags + # ProjectUri, LicenseUri, IconUri, ReleaseNotes, PrivateData, PassThru, Force + + # currently testing with: FilePath, RequiredModules diff --git a/test/TestPSScriptFileInfo.Tests.ps1 b/test/TestPSScriptFileInfo.Tests.ps1 index 2399efeaf..7d0b35c67 100644 --- a/test/TestPSScriptFileInfo.Tests.ps1 +++ b/test/TestPSScriptFileInfo.Tests.ps1 @@ -4,14 +4,34 @@ Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force Describe "Test New-PSScriptFileInfo" { - BeforeEach { + BeforeAll { $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) 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" } - AfterEach { + # BeforeEach { + # $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + # $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" + # $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" + # $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) + # Get-NewTestDirs($tmpDirPaths) + # } + # AfterEach { + # $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + # $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" + # $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" + # $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) + # Get-RemoveTestDirs($tmpDirPaths) + # } + AfterAll { $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" @@ -19,14 +39,55 @@ Describe "Test New-PSScriptFileInfo" { Get-RemoveTestDirs($tmpDirPaths) } - It "create .ps1 file with minimal required fields" { + It "determine script file with minimal required fields as valid" { $pathTestRes = Test-Path $tmpDir1Path $pathTestRes | Should -Be $true Write-Host $pathTestRes $basicScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "basicTestScript.ps1" Write-Host $basicScriptFilePath $scriptDescription = "this is a test script" - # $res = New-PSScriptFileInfo -FilePath $basicScriptFilePath -Description $scriptDescription -PassThru - # $res.Description | Should -Be $scriptDescription + $res = New-PSScriptFileInfo -FilePath $basicScriptFilePath -Description $scriptDescription -PassThru + Write-Host $res + Test-PSScriptFileInfo $basicScriptFilePath | 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 + # TODO: how to test for warnings? (psScriptMissingAuthor) + } + + 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 + # TODO: how to test for warnings? (psScriptMissingDescription) + } + + 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 + # TODO: how to test for warnings? (PSScriptMissingHelpContentCommentBlock) + } + + It "not determine script file Guid as valid" { + $scriptName = "InvalidScriptMissingGuid.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + Test-PSScriptFileInfo $scriptFilePath | Should -Be $false + # TODO: how to test for warnings? (psScriptMissingGuid) + } + + 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 + # TODO: how to test for warnings? (psScriptMissingVersion) } } From 2da70ca2e60709196cfb57e694a4384f395f3ab2 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 6 Jun 2022 11:35:24 -0400 Subject: [PATCH 51/86] clean up comments in test --- test/NewPSScriptFileInfo.Tests.ps1 | 4 +- test/TestPSScriptFileInfo.Tests.ps1 | 15 +---- test/UpdatePSScriptFileInfo.Tests.ps1 | 80 +++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 test/UpdatePSScriptFileInfo.Tests.ps1 diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index c486663dc..301e8335b 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -4,14 +4,14 @@ Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force Describe "Test New-PSScriptFileInfo" { - BeforeEach { + BeforeAll { $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) Get-NewTestDirs($tmpDirPaths) } - AfterEach { + AfterAll { $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" diff --git a/test/TestPSScriptFileInfo.Tests.ps1 b/test/TestPSScriptFileInfo.Tests.ps1 index 7d0b35c67..adf18bb3c 100644 --- a/test/TestPSScriptFileInfo.Tests.ps1 +++ b/test/TestPSScriptFileInfo.Tests.ps1 @@ -17,20 +17,7 @@ Describe "Test New-PSScriptFileInfo" { # Path to specifically to that invalid test scripts folder $script:testScriptsFolderPath = Join-Path $testFilesFolderPath -ChildPath "testScripts" } - # BeforeEach { - # $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" - # $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" - # $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" - # $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) - # Get-NewTestDirs($tmpDirPaths) - # } - # AfterEach { - # $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" - # $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" - # $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" - # $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) - # Get-RemoveTestDirs($tmpDirPaths) - # } + AfterAll { $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" diff --git a/test/UpdatePSScriptFileInfo.Tests.ps1 b/test/UpdatePSScriptFileInfo.Tests.ps1 new file mode 100644 index 000000000..adf18bb3c --- /dev/null +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -0,0 +1,80 @@ +# 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" + $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" + $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" + $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) + 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" + } + + AfterAll { + $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" + $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" + $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" + $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) + Get-RemoveTestDirs($tmpDirPaths) + } + + It "determine script file with minimal required fields as valid" { + $pathTestRes = Test-Path $tmpDir1Path + $pathTestRes | Should -Be $true + Write-Host $pathTestRes + $basicScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "basicTestScript.ps1" + Write-Host $basicScriptFilePath + $scriptDescription = "this is a test script" + $res = New-PSScriptFileInfo -FilePath $basicScriptFilePath -Description $scriptDescription -PassThru + Write-Host $res + Test-PSScriptFileInfo $basicScriptFilePath | 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 + # TODO: how to test for warnings? (psScriptMissingAuthor) + } + + 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 + # TODO: how to test for warnings? (psScriptMissingDescription) + } + + 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 + # TODO: how to test for warnings? (PSScriptMissingHelpContentCommentBlock) + } + + It "not determine script file Guid as valid" { + $scriptName = "InvalidScriptMissingGuid.ps1" + $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + + Test-PSScriptFileInfo $scriptFilePath | Should -Be $false + # TODO: how to test for warnings? (psScriptMissingGuid) + } + + 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 + # TODO: how to test for warnings? (psScriptMissingVersion) + } +} From f58e6ce089520f3b526018e1ab4fcf111065fd16 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 6 Jun 2022 19:56:53 -0400 Subject: [PATCH 52/86] clean up code and tests --- src/code/PSScriptFileInfo.cs | 540 +++++++++++++------------- src/code/UpdatePSScriptFileInfo.cs | 3 +- test/NewPSScriptFileInfo.Tests.ps1 | 22 +- test/TestPSScriptFileInfo.Tests.ps1 | 11 +- test/UpdatePSScriptFileInfo.Tests.ps1 | 137 +++++-- 5 files changed, 377 insertions(+), 336 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 8ae78aebd..a07518629 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Collections.ObjectModel; using Microsoft.PowerShell.Commands; +using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { @@ -29,7 +30,7 @@ public sealed class PSScriptFileInfo /// /// the Version of the script /// - public Version Version { get; set; } + public NuGetVersion Version { get; set; } /// /// the GUID for the script @@ -204,7 +205,7 @@ string description author = Environment.UserName; } - Version = !String.IsNullOrEmpty(version) ? new Version (version) : new Version("1.0.0.0"); + 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; @@ -235,10 +236,9 @@ public string[] GetFileContents() return fileContents; } - public void SetFileContents(string[] newFileContents) + public void SetFileContents(string[] fileUpdatedContents) { - // TODO: new file contents is a misleading name...rename - fileContents = newFileContents; + fileContents = fileUpdatedContents; } #endregion @@ -258,278 +258,298 @@ public static bool TryParseScriptFile( List errorsList = new List(); bool successfullyParsed = false; - if (scriptFileInfoPath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + // a valid example script will have this format: + /* <#PSScriptInfo + .VERSION 1.6 + .GUID abf490023 - 9128 - 4323 - sdf9a - jf209888ajkl + .AUTHOR Jane Doe + .COMPANYNAME Microsoft + .COPYRIGHT + .TAGS Windows MacOS + #> + + <# + + .SYNOPSIS + Synopsis description here + .DESCRIPTION + Description here + .PARAMETER Name + .EXAMPLE + Example cmdlet here + + #> + */ + + if (!scriptFileInfoPath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { - // Parse the script file - var ast = Parser.ParseFile( - scriptFileInfoPath, - out Token[] tokens, - out ParseError[] parserErrors); + 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); + } - if (parserErrors.Length > 0 && !String.Equals(parserErrors[0].ErrorId, "WorkflowNotSupportedInPowerShellCore", StringComparison.OrdinalIgnoreCase)) + // Parse the script file + var ast = Parser.ParseFile( + scriptFileInfoPath, + out Token[] tokens, + out ParseError[] parserErrors); + + if (parserErrors.Length > 0) + { + foreach (ParseError err in parserErrors) { - // TODO: do we want to completely ignore WorkFlowNotSupportedInPowerShellCore error (not even write) - // or do we want to write, but not return if it's just that error? - 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 ArgumentException(message); var psScriptFileParseError = new ErrorRecord(ex, err.ErrorId, ErrorCategory.ParserError, null); errorsList.Add(psScriptFileParseError); - errors = errorsList.ToArray(); - } - - return successfullyParsed; } - else if (ast != null) - { - // Get the block/group comment beginning with <#PSScriptInfo - Hashtable parsedPSScriptInfoHashtable = new Hashtable(); - List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); - string commentPattern = "<#PSScriptInfo"; - Regex rg = new Regex(commentPattern); - List psScriptInfoCommentTokens = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).ToList(); - - if (psScriptInfoCommentTokens.Count() == 0 || psScriptInfoCommentTokens[0] == 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 successfullyParsed; - } + errors = errorsList.ToArray(); + return successfullyParsed; + } - string[] commentLines = Regex.Split(psScriptInfoCommentTokens[0].Text, "[\r\n]"); - string keyName = String.Empty; - string value = String.Empty; + if (ast == null) + { + var astNullMessage = String.Format(".ps1 file was parsed but AST was null"); + var astNullEx = new ArgumentException(astNullMessage); + var astCouldNotBeCreatedError = new ErrorRecord(astNullEx, "ASTCouldNotBeCreated", ErrorCategory.ParserError, null); - /** - If comment line count is not more than two, it doesn't have the any metadata property - comment block would look like: + errorsList.Add(astCouldNotBeCreatedError); + errors = errorsList.ToArray(); + return successfullyParsed; + } - <#PSScriptInfo - #> - */ + // Get the block/group comment beginning with <#PSScriptInfo + Hashtable parsedPSScriptInfoHashtable = new Hashtable(); + List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); + string commentPattern = "<#PSScriptInfo"; + Regex rg = new Regex(commentPattern); + List psScriptInfoCommentTokens = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).ToList(); - if (commentLines.Count() > 2) - { - // TODO: is it an error if the metadata property is empty? - for (int i = 1; i < commentLines.Count(); i++) - { - string line = commentLines[i]; - if (String.IsNullOrEmpty(line)) - { - continue; - } - - // A line is starting with . conveys a new metadata property - if (line.Trim().StartsWith(".")) - { - string[] parts = line.Trim().TrimStart('.').Split(); - keyName = parts[0]; - value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; - parsedPSScriptInfoHashtable.Add(keyName, value); - } - } - - successfullyParsed = true; - } + if (psScriptInfoCommentTokens.Count() == 0 || psScriptInfoCommentTokens[0] == 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); - // get .DESCRIPTION comment - 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; - } + errors = errorsList.ToArray(); + return successfullyParsed; + } + + string[] commentLines = Regex.Split(psScriptInfoCommentTokens[0].Text, "[\r\n]"); + 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 + comment block would look like: - if (!String.IsNullOrEmpty(scriptCommentInfo.Description) && !scriptCommentInfo.Description.Contains("<#") && !scriptCommentInfo.Description.Contains("#>")) + <#PSScriptInfo + #> + */ + + if (commentLines.Count() > 2) + { + // TODO: is it an error if the aforementioned metadata property is empty? + for (int i = 1; i < commentLines.Count(); i++) + { + string line = commentLines[i]; + if (String.IsNullOrEmpty(line)) { - parsedPSScriptInfoHashtable.Add("DESCRIPTION", scriptCommentInfo.Description); + continue; } - else + + // A line is starting with . conveys a new metadata property + if (line.Trim().StartsWith(".")) { - 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(); - successfullyParsed = false; - return successfullyParsed; + string[] parts = line.Trim().TrimStart('.').Split(); + keyName = parts[0]; + value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; + parsedPSScriptInfoHashtable.Add(keyName, value); } + } + successfullyParsed = true; + } - // get RequiredModules - ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; - ReadOnlyCollection parsedModules = new List().AsReadOnly(); + // get .DESCRIPTION comment + 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(); + successfullyParsed = false; + return successfullyParsed; + } - if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) - { - parsedModules = parsedScriptRequirements.RequiredModules; - parsedPSScriptInfoHashtable.Add("RequiredModules", parsedModules); - } + if (!String.IsNullOrEmpty(scriptCommentInfo.Description) && !scriptCommentInfo.Description.Contains("<#") && !scriptCommentInfo.Description.Contains("#>")) + { + parsedPSScriptInfoHashtable.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(); + successfullyParsed = false; + return successfullyParsed; + } - // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow - // TODO: DefinedWorkflow is no longer supported as of PowerShellCore 6+, do we ignore error or reject script? - var parsedFunctionAst = ast.FindAll(a => a is FunctionDefinitionAst, true); - List allCommands = new List(); - if (allCommands.Count() > 0) - { - foreach (var function in parsedFunctionAst) - { - allCommands.Add((FunctionDefinitionAst) function); - } - List allCommandNames = allCommands.Select(a => a.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedCommands", allCommandNames); + // get RequiredModules + ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; + ReadOnlyCollection parsedModules = new List().AsReadOnly(); - // b.Body.Extent.Text to get actual content. So "Function b.Name c.Body.Extent.Text" is that whole line - List allFunctionNames = allCommands.Where(a => !a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedFunctions", allFunctionNames); + if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) + { + parsedModules = parsedScriptRequirements.RequiredModules; + parsedPSScriptInfoHashtable.Add("RequiredModules", parsedModules); + } - List allWorkflowNames = allCommands.Where(a => a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); - } + // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow + // DefinedWorkflow is no longer supported as of PowerShellCore 6+, but we ignore error + var parsedFunctionAst = ast.FindAll(a => a is FunctionDefinitionAst, true); + List allCommands = new List(); + if (allCommands.Count() > 0) + { + foreach (var function in parsedFunctionAst) + { + allCommands.Add((FunctionDefinitionAst) function); + } - if (!parsedPSScriptInfoHashtable.ContainsKey("VERSION") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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); - errors = errorsList.ToArray(); - successfullyParsed = false; - return successfullyParsed; - } + List allCommandNames = allCommands.Select(a => a.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedCommands", allCommandNames); - if (!parsedPSScriptInfoHashtable.ContainsKey("AUTHOR") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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); - errors = errorsList.ToArray(); - successfullyParsed = false; - return successfullyParsed; - } + // b.Body.Extent.Text to get actual content. So "Function b.Name c.Body.Extent.Text" is that whole line + List allFunctionNames = allCommands.Where(a => !a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedFunctions", allFunctionNames); - if (!parsedPSScriptInfoHashtable.ContainsKey("GUID") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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(); - successfullyParsed = false; - return successfullyParsed; - } + List allWorkflowNames = allCommands.Where(a => a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); + parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); + } + + if (!parsedPSScriptInfoHashtable.ContainsKey("VERSION") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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); + errors = errorsList.ToArray(); + successfullyParsed = false; + return successfullyParsed; + } + if (!parsedPSScriptInfoHashtable.ContainsKey("AUTHOR") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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); + errors = errorsList.ToArray(); + successfullyParsed = false; + return successfullyParsed; + } - string parsedVersion = (string) parsedPSScriptInfoHashtable["VERSION"]; - string parsedAuthor = (string) parsedPSScriptInfoHashtable["AUTHOR"]; - Guid parsedGuid = new Guid((string) parsedPSScriptInfoHashtable["GUID"]); + if (!parsedPSScriptInfoHashtable.ContainsKey("GUID") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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(); + successfullyParsed = false; + return successfullyParsed; + } - // if (String.IsNullOrEmpty(parsedVersion) || String.IsNullOrEmpty(parsedAuthor) || parsedGuid == Guid.Empty) - // { - // var message = String.Format("PSScript file is missing one of the following required properties: Version, Author, Guid"); - // var ex = new ArgumentException(message); - // var psScriptMissingRequiredPropertyError = new ErrorRecord(ex, "psScriptMissingRequiredProperty", ErrorCategory.ParserError, null); - // errorsList.Add(psScriptMissingRequiredPropertyError); - // errors = errorsList.ToArray(); - // successfullyParsed = false; - // return successfullyParsed; - // } + string parsedVersion = (string) parsedPSScriptInfoHashtable["VERSION"]; + string parsedAuthor = (string) parsedPSScriptInfoHashtable["AUTHOR"]; + Guid parsedGuid = new Guid((string) parsedPSScriptInfoHashtable["GUID"]); - try - { - char[] spaceDelimeter = new char[]{' '}; - string[] parsedTags = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["TAGS"]); - string[] parsedExternalModuleDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["EXTERNALMODULEDEPENDENCIES"]); - string[] parsedRequiredScripts = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["REQUIREDSCRIPTS"]); - string[] parsedExternalScriptDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["EXTERNALSCRIPTDEPENDENCIES"]); - string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["RELEASENOTES"]); - Uri parsedLicenseUri = null; - Uri parsedProjectUri = null; - Uri parsedIconUri = null; - - if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["LICENSEURI"])) - { - Uri.TryCreate((string) parsedPSScriptInfoHashtable["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri); - } - - if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["PROJECTURI"])) - { - Uri.TryCreate((string) parsedPSScriptInfoHashtable["PROJECTURI"], UriKind.Absolute, out parsedProjectUri); - } - - if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["ICONURI"])) - { - Uri.TryCreate((string) parsedPSScriptInfoHashtable["ICONURI"], UriKind.Absolute, out parsedProjectUri); - } - - // parsedPSScriptInfoHashtable 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: (string) parsedPSScriptInfoHashtable["COMPANYNAME"], - copyright: (string) parsedPSScriptInfoHashtable["COPYRIGHT"], - tags: parsedTags, - licenseUri: parsedLicenseUri, - projectUri: parsedProjectUri, - iconUri: parsedIconUri, - requiredModules: parsedModules.ToArray(), - externalModuleDependencies: parsedExternalModuleDependencies, - requiredScripts: parsedRequiredScripts, - externalScriptDependencies: parsedExternalScriptDependencies, - releaseNotes: parsedReleaseNotes, - privateData: (string) parsedPSScriptInfoHashtable["PRIVATEDATA"], - description: scriptCommentInfo.Description); - - // TODO: get file contents and set member of PSScriptFileInfo instance - // Or should I get end of file contents just right here? - string[] totalFileContents = File.ReadAllLines(scriptFileInfoPath); - var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); - var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); - if (contentAfterDescription.Count() > 0) - { - parsedScript.SetFileContents(contentAfterDescription.ToArray()); - } - } - 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); - successfullyParsed = false; - } + try + { + char[] spaceDelimeter = new char[]{' '}; + string[] parsedTags = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["TAGS"]); + string[] parsedExternalModuleDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["EXTERNALMODULEDEPENDENCIES"]); + string[] parsedRequiredScripts = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["REQUIREDSCRIPTS"]); + string[] parsedExternalScriptDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["EXTERNALSCRIPTDEPENDENCIES"]); + string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["RELEASENOTES"]); + Uri parsedLicenseUri = null; + Uri parsedProjectUri = null; + Uri parsedIconUri = null; + + if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["LICENSEURI"])) + { + Uri.TryCreate((string) parsedPSScriptInfoHashtable["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri); + } + + if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["PROJECTURI"])) + { + Uri.TryCreate((string) parsedPSScriptInfoHashtable["PROJECTURI"], UriKind.Absolute, out parsedProjectUri); + } + + if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["ICONURI"])) + { + Uri.TryCreate((string) parsedPSScriptInfoHashtable["ICONURI"], UriKind.Absolute, out parsedProjectUri); } - else + + // parsedPSScriptInfoHashtable 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: (string) parsedPSScriptInfoHashtable["COMPANYNAME"], + copyright: (string) parsedPSScriptInfoHashtable["COPYRIGHT"], + tags: parsedTags, + licenseUri: parsedLicenseUri, + projectUri: parsedProjectUri, + iconUri: parsedIconUri, + requiredModules: parsedModules.ToArray(), + externalModuleDependencies: parsedExternalModuleDependencies, + requiredScripts: parsedRequiredScripts, + externalScriptDependencies: parsedExternalScriptDependencies, + releaseNotes: parsedReleaseNotes, + privateData: (string) parsedPSScriptInfoHashtable["PRIVATEDATA"], + description: scriptCommentInfo.Description); + + // get file contents and set member of PSScriptFileInfo instance + string[] totalFileContents = File.ReadAllLines(scriptFileInfoPath); + var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); + var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); + if (contentAfterDescription.Count() > 0) { - var message = String.Format(".ps1 file was parsed but AST was null"); - var ex = new ArgumentException(message); - var astCouldNotBeCreatedError = new ErrorRecord(ex, "ASTCouldNotBeCreated", ErrorCategory.ParserError, null); - errorsList.Add(astCouldNotBeCreatedError); - return successfullyParsed; + parsedScript.SetFileContents(contentAfterDescription.ToArray()); } + + } + 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(); + successfullyParsed = false; } + return successfullyParsed; } /// /// 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 - /// TODO: should this really be static? I think not, but also we create a new instance for the updated PSScriptFileInfo so as to not data loss /// public static bool TryUpdateScriptFile( ref PSScriptFileInfo originalScript, @@ -575,14 +595,16 @@ public static bool TryUpdateScriptFile( { if (!String.IsNullOrEmpty(version)) { - if (!System.Version.TryParse(version, out Version updatedVersion)) + if (!NuGetVersion.TryParse(version, out NuGetVersion updatedVersion)) { - updatedScript.Version = new Version("2.0.0.0"); - } - else - { - updatedScript.Version = 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 successfullyUpdated; } + updatedScript.Version = updatedVersion; } if (guid != Guid.Empty) @@ -675,7 +697,6 @@ public static bool TryUpdateScriptFile( return successfullyUpdated; } - // TODO: get end of file contents here if (updatedScript.GetFileContents() != null && updatedScript.GetFileContents().Length > 0) { psScriptFileContents += "\n" + String.Join("\n", updatedScript.GetFileContents()); @@ -804,6 +825,7 @@ Feature 2 Feature 3 Feature 4 Feature 5 + .PRIVATEDATA #> */ @@ -832,6 +854,7 @@ Feature 5 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}", String.Join("\n", ReleaseNotes))); + psScriptInfoLines.Add(String.Format(".PRIVATEDATA\n{0}", PrivateData)); psScriptInfoLines.Add("#>"); pSScriptInfoString = String.Join("\n\n", psScriptInfoLines); @@ -944,54 +967,11 @@ out ErrorRecord error psHelpInfoLines.Add(String.Format(".FUNCTIONALITY\n{0}", String.Join("\n", Functionality))); } - // TODO: add newline before #> or will that just inflate it with newlines each round? psHelpInfoLines.Add("#>"); psHelpInfo = String.Join("\n", psHelpInfoLines); return psHelpInfoSuccessfullyCreated; } - - // public void GetEndOfFileLinesContent2( - // string totalFileContents2, - // out string endOfFileContent) - // { - // // if (String.IsNullOrEmpty(filePath) || !filePath.EndsWith(".ps1")) - // // { - // // return; - // // } - - // // string[] totalFileContents = File.ReadAllLines(filePath); - // // var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); - - // // var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); - - // // if (contentAfterDescription.Count() > 0) - // // { - // // endOfFileContent = String.Join("\n", contentAfterDescription); - // // } - // } - public void GetEndOfFileLinesContent( - string filePath, - out string endOfFileContent) - { - endOfFileContent = String.Empty; - - // if (String.IsNullOrEmpty(filePath) || !filePath.EndsWith(".ps1")) - // { - // return; - // } - - // string[] totalFileContents = File.ReadAllLines(filePath); - // var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); - - // var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); - - // if (contentAfterDescription.Count() > 0) - // { - // endOfFileContent = String.Join("\n", contentAfterDescription); - // } - } - /// /// Ensure no fields (passed as stringToValidate) contains '<#' or '#>' (would break comment section) /// diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 445a1a0a5..3cd29013f 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -198,7 +198,6 @@ protected override void ProcessRecord() ThrowTerminatingError(iconErrorRecord); } - // TODO: must resolve relative paths before checking if path exists if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { var exMessage = "File path needs to end with a .ps1 extension. Example: C:/Users/john/x/MyScript.ps1"; @@ -324,7 +323,7 @@ protected override void ProcessRecord() if (PassThru) { - WriteObject(parsedScriptFileInfo); + WriteObject(updatedPSScriptFileContents); } } } diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index 301e8335b..80fa4808d 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -11,6 +11,13 @@ Describe "Test New-PSScriptFileInfo" { $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) Get-NewTestDirs($tmpDirPaths) } + AfterEach { + $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testScript.ps1" + if (Test-Path -Path $scriptFilePath) + { + Remove-Item $scriptFilePath + } + } AfterAll { $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" @@ -20,10 +27,9 @@ Describe "Test New-PSScriptFileInfo" { } It "create .ps1 file with minimal required fields" { - $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testScript2.ps1" + $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testScript.ps1" $scriptDescription = "this is a test script" $res = New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -PassThru - Write-host $res $res | Should -Not -BeNullOrEmpty Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue @@ -31,14 +37,13 @@ Describe "Test New-PSScriptFileInfo" { It "create .ps1 file with relative path" { $relativeCurrentPath = Get-Location - $scriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "testScript2.ps1" + $scriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "testScript.ps1" $scriptDescription = "this is a test script" $res = New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -PassThru - Write-host $res $res | Should -Not -BeNullOrEmpty Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue - Remove-Item -Path (Join-Path -Path $relativeCurrentPath -ChildPath "testScript2.ps1") + Remove-Item -Path (Join-Path -Path $relativeCurrentPath -ChildPath "testScript.ps1") } It "create .ps1 file with RequiredModules" { @@ -52,20 +57,15 @@ Describe "Test New-PSScriptFileInfo" { $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testScript2.ps1" $scriptDescription = "this is a test script" $res = New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -RequiredModules $requiredModulesHashtables -PassThru - Write-Host $res $res | Should -Not -BeNullOrEmpty Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue } - # test with each param really....would be easier to test if we returned PSSriptFileInfoobject + # TODO: test with each param really....would be easier to test if we returned PSSriptFileInfoObject with PassThru # FilePath, Version, Author, Description, Guid, CompanyName, Copyright, RequiredModules, # ExternalModuleDependencies, RequiredScripts, ExternalScriptDependencies, Tags # ProjectUri, LicenseUri, IconUri, ReleaseNotes, PrivateData, PassThru, Force # currently testing with: FilePath, RequiredModules - - - - } diff --git a/test/TestPSScriptFileInfo.Tests.ps1 b/test/TestPSScriptFileInfo.Tests.ps1 index adf18bb3c..41f5cc1e2 100644 --- a/test/TestPSScriptFileInfo.Tests.ps1 +++ b/test/TestPSScriptFileInfo.Tests.ps1 @@ -26,15 +26,10 @@ Describe "Test New-PSScriptFileInfo" { Get-RemoveTestDirs($tmpDirPaths) } - It "determine script file with minimal required fields as valid" { - $pathTestRes = Test-Path $tmpDir1Path - $pathTestRes | Should -Be $true - Write-Host $pathTestRes - $basicScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "basicTestScript.ps1" - Write-Host $basicScriptFilePath + It "determine script file with minimal required fields as valid" { + $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "basicTestScript.ps1" $scriptDescription = "this is a test script" - $res = New-PSScriptFileInfo -FilePath $basicScriptFilePath -Description $scriptDescription -PassThru - Write-Host $res + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription Test-PSScriptFileInfo $basicScriptFilePath | Should -Be $true } diff --git a/test/UpdatePSScriptFileInfo.Tests.ps1 b/test/UpdatePSScriptFileInfo.Tests.ps1 index adf18bb3c..a89c8a191 100644 --- a/test/UpdatePSScriptFileInfo.Tests.ps1 +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -18,6 +18,20 @@ Describe "Test New-PSScriptFileInfo" { $script:testScriptsFolderPath = Join-Path $testFilesFolderPath -ChildPath "testScripts" } + BeforeEach { + $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testscript.ps1" + $scriptDescription = "this is a test script" + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -PassThru + } + + AfterEach { + $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testscript.ps1" + if (Test-Path -Path $scriptFilePath) + { + Remove-Item $scriptFilePath + } + } + AfterAll { $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" @@ -26,55 +40,108 @@ Describe "Test New-PSScriptFileInfo" { Get-RemoveTestDirs($tmpDirPaths) } - It "determine script file with minimal required fields as valid" { - $pathTestRes = Test-Path $tmpDir1Path - $pathTestRes | Should -Be $true - Write-Host $pathTestRes - $basicScriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "basicTestScript.ps1" - Write-Host $basicScriptFilePath - $scriptDescription = "this is a test script" - $res = New-PSScriptFileInfo -FilePath $basicScriptFilePath -Description $scriptDescription -PassThru - Write-Host $res - Test-PSScriptFileInfo $basicScriptFilePath | Should -Be $true + It "update script file Author property" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -Author "JohnDoe" + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } + + It "update script file Version property" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -Version "2.0.0.0" + 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 + It "update script file Version property with prerelease version" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -Version "3.0.0-alpha" + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } - Test-PSScriptFileInfo $scriptFilePath | Should -Be $false - # TODO: how to test for warnings? (psScriptMissingAuthor) + It "not update script file with invalid version" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -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 "not determine script file with Description field missing as valid" { - $scriptName = "InvalidScriptMissingDescription.ps1" - $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + It "update script file Description property" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -Description "this is an updated test script" + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } - Test-PSScriptFileInfo $scriptFilePath | Should -Be $false - # TODO: how to test for warnings? (psScriptMissingDescription) + It "update script file Guid property" { + $testGuid = [Guid]::NewGuid(); + Update-PSScriptFileInfo -FilePath $scriptFilePath -Guid $testGuid + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true } - It "not determine script that is missing Description block altogether as valid" { - $scriptName = "InvalidScriptMissingDescriptionCommentBlock.ps1" - $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + It "update script file CompanyName property" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -CompanyName "Microsoft Corporation" + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } - Test-PSScriptFileInfo $scriptFilePath | Should -Be $false - # TODO: how to test for warnings? (PSScriptMissingHelpContentCommentBlock) + It "update script file Copyright property" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -Copyright "(c) 2022 Microsoft Corporation. All rights reserved" + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true } - It "not determine script file Guid as valid" { - $scriptName = "InvalidScriptMissingGuid.ps1" - $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + It "update script file ExternalModuleDependencies property" { + $testExternalModuleDependencies = @("PowerShellGet", "PackageManagement") + Update-PSScriptFileInfo -FilePath $scriptFilePath -ExternalModuleDependencies $testExternalModuleDependencies + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } - Test-PSScriptFileInfo $scriptFilePath | Should -Be $false - # TODO: how to test for warnings? (psScriptMissingGuid) + It "update script file ExternalScriptDependencies property" { + $testExternalScriptDependencies = @("Required-Script1", "Required-Script2") + Update-PSScriptFileInfo -FilePath $scriptFilePath -ExternalScriptDependencies $testExternalScriptDependencies + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true } - It "not determine script file missing Version as valid" { - $scriptName = "InvalidScriptMissingVersion.ps1" - $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName + It "update script file IconUri property" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -IconUri "https://testscript.com/icon" + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } - Test-PSScriptFileInfo $scriptFilePath | Should -Be $false - # TODO: how to test for warnings? (psScriptMissingVersion) + It "update script file LicenseUri property" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -LicenseUri "https://testscript.com/license" + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true } + + It "update script file ProjectUri property" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -ProjectUri "https://testscript.com/" + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } + + It "update script file PrivateData property" { + Update-PSScriptFileInfo -FilePath $scriptFilePath -PrivateData "this is some private data" + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } + + It "update script file ReleaseNotes property" { + $testReleaseNotes = @("release 3.0.12 includes bug fixes", "release 3.0.13 includes feature requests") + Update-PSScriptFileInfo -FilePath $scriptFilePath -ReleaseNotes $testReleaseNotes + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } + + 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"} + $testRequiredModules = $hashtable1, $hashtable2, $hashtable3, $hashtable4 + + Update-PSScriptFileInfo -FilePath $scriptFilePath -RequiredModules $testRequiredModules + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } + + It "update script file RequiredScripts property" { + $testRequiredScripts = @("Required-Script1", "Required-Script2") + Update-PSScriptFileInfo -FilePath $scriptFilePath -RequiredScripts $testRequiredScripts + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } + + It "update script file Tags property" { + $testTags = @("Tag1", "Tag2") + Update-PSScriptFileInfo -FilePath $scriptFilePath -Tags $testTags + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true + } + + # Validate param needs to be tested } From 37aeb37c7f5b737a145031af0c10b1e1138d97de Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 7 Jun 2022 09:18:08 -0400 Subject: [PATCH 53/86] fix bug in test --- test/TestPSScriptFileInfo.Tests.ps1 | 6 +++--- test/UpdatePSScriptFileInfo.Tests.ps1 | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/TestPSScriptFileInfo.Tests.ps1 b/test/TestPSScriptFileInfo.Tests.ps1 index 41f5cc1e2..356dd184c 100644 --- a/test/TestPSScriptFileInfo.Tests.ps1 +++ b/test/TestPSScriptFileInfo.Tests.ps1 @@ -3,7 +3,7 @@ Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force -Describe "Test New-PSScriptFileInfo" { +Describe "Test Test-PSScriptFileInfo" { BeforeAll { $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" @@ -27,10 +27,10 @@ Describe "Test New-PSScriptFileInfo" { } It "determine script file with minimal required fields as valid" { - $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "basicTestScript.ps1" + $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testscript.ps1" $scriptDescription = "this is a test script" New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription - Test-PSScriptFileInfo $basicScriptFilePath | Should -Be $true + Test-PSScriptFileInfo $scriptFilePath | Should -Be $true } It "not determine script file with Author field missing as valid" { diff --git a/test/UpdatePSScriptFileInfo.Tests.ps1 b/test/UpdatePSScriptFileInfo.Tests.ps1 index a89c8a191..4138c616d 100644 --- a/test/UpdatePSScriptFileInfo.Tests.ps1 +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -3,7 +3,7 @@ Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force -Describe "Test New-PSScriptFileInfo" { +Describe "Test Update-PSScriptFileInfo" { BeforeAll { $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" From 8e7b6c27e5485f0bc80eee1d3968e659ce687d07 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 8 Jun 2022 12:47:45 -0400 Subject: [PATCH 54/86] push changes temporarily --- src/code/PSScriptFileInfo.cs | 65 ++++++++++++++---------------------- 1 file changed, 25 insertions(+), 40 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index a07518629..94559a774 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -17,12 +17,14 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { /// - /// This class contains information for a repository item. + /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents) /// public sealed class PSScriptFileInfo { #region Members + private string[] fileContents = new string[]{}; + #endregion #region Properties @@ -76,8 +78,6 @@ public sealed class PSScriptFileInfo /// The list of modules required by the script /// Hashtable keys: GUID, MaxVersion, ModuleName (Required), RequiredVersion, Version /// - [Parameter] - [ValidateNotNullOrEmpty()] public ModuleSpecification[] RequiredModules { get; set; } = new ModuleSpecification[]{}; /// @@ -103,83 +103,62 @@ public sealed class PSScriptFileInfo /// /// The private data associated with the script /// - [Parameter] - [ValidateNotNullOrEmpty()] public string PrivateData { get; set; } /// /// The description of the script /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty()] public string Description { get; set; } /// /// The synopsis of the script /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty()] public string Synopsis { get; set; } /// /// The example(s) relating to the script's usage /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty()] public string[] Example { get; set; } = new string[]{}; /// /// The inputs to the script /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty()] public string[] Inputs { get; set; } = new string[]{}; /// /// The outputs to the script /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty()] public string[] Outputs { get; set; } = new string[]{}; /// /// The notes for the script /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty()] public string[] Notes { get; set; } = new string[]{}; /// /// The links for the script /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty()] public string[] Links { get; set; } = new string[]{}; /// /// TODO: what is this? /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty()] public string[] Component { get; set; } = new string[]{}; /// /// TODO: what is this? /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty()] public string[] Role { get; set; } = new string[]{}; /// /// TODO: what is this? /// - [Parameter(Mandatory = true)] - [ValidateNotNullOrEmpty()] public string[] Functionality { get; set; } = new string[]{}; #endregion #region Constructor + private PSScriptFileInfo() {} public PSScriptFileInfo( string version, @@ -243,21 +222,16 @@ public void SetFileContents(string[] fileUpdatedContents) #endregion - #region Public Static Methods + #region Internal Static Methods /// /// Tests the contents of the .ps1 file at the provided path /// - public static bool TryParseScriptFile( + internal static bool TryParseScriptFile( string scriptFileInfoPath, out PSScriptFileInfo parsedScript, out ErrorRecord[] errors) { - parsedScript = null; - errors = new ErrorRecord[]{}; - List errorsList = new List(); - bool successfullyParsed = false; - // a valid example script will have this format: /* <#PSScriptInfo .VERSION 1.6 @@ -281,12 +255,18 @@ Example cmdlet here #> */ + parsedScript = null; + errors = new ErrorRecord[]{}; + List errorsList = 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; } // Parse the script file @@ -294,34 +274,39 @@ Example cmdlet here 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 ArgumentException(message); + var ex = new InvalidOperationException(message); var psScriptFileParseError = new ErrorRecord(ex, err.ErrorId, ErrorCategory.ParserError, null); errorsList.Add(psScriptFileParseError); + parseSuccessful = false; } } - errors = errorsList.ToArray(); - return successfullyParsed; + if (!parseSuccessful) + { + errors = errorsList.ToArray(); + return parseSuccessful; + } } if (ast == null) { - var astNullMessage = String.Format(".ps1 file was parsed but AST was null"); - var astNullEx = new ArgumentException(astNullMessage); - var astCouldNotBeCreatedError = new ErrorRecord(astNullEx, "ASTCouldNotBeCreated", ErrorCategory.ParserError, null); + var astNullInnerEx = new ParseException("Parsed AST was null for .ps1 file"); + var parseFileEx = new InvalidOperationException("Cannot parse .ps1 file", astNullInnerEx); //TODO: nest this formatting wise with named args + var astCouldNotBeCreatedError = new ErrorRecord(parseFileEx, "ASTCouldNotBeCreated", ErrorCategory.ParserError, null); errorsList.Add(astCouldNotBeCreatedError); errors = errorsList.ToArray(); - return successfullyParsed; + return false; } // Get the block/group comment beginning with <#PSScriptInfo From b20f73d0b91a95da16597a365e62818ebf2c9c39 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 9 Jun 2022 16:06:08 -0400 Subject: [PATCH 55/86] remove succcesfullyParsed var --- src/code/PSScriptFileInfo.cs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 94559a774..43c328334 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -324,7 +324,7 @@ Example cmdlet here errorsList.Add(psCommentMissingError); errors = errorsList.ToArray(); - return successfullyParsed; + return false; } string[] commentLines = Regex.Split(psScriptInfoCommentTokens[0].Text, "[\r\n]"); @@ -359,8 +359,6 @@ Example cmdlet here parsedPSScriptInfoHashtable.Add(keyName, value); } } - - successfullyParsed = true; } // get .DESCRIPTION comment @@ -372,8 +370,7 @@ Example cmdlet here var psScriptMissingHelpContentCommentBlockError = new ErrorRecord(ex, "PSScriptMissingHelpContentCommentBlock", ErrorCategory.ParserError, null); errorsList.Add(psScriptMissingHelpContentCommentBlockError); errors = errorsList.ToArray(); - successfullyParsed = false; - return successfullyParsed; + return false; } if (!String.IsNullOrEmpty(scriptCommentInfo.Description) && !scriptCommentInfo.Description.Contains("<#") && !scriptCommentInfo.Description.Contains("#>")) @@ -387,8 +384,7 @@ Example cmdlet here var psScriptMissingDescriptionOrInvalidPropertyError = new ErrorRecord(ex, "psScriptDescriptionMissingOrInvalidDescription", ErrorCategory.ParserError, null); errorsList.Add(psScriptMissingDescriptionOrInvalidPropertyError); errors = errorsList.ToArray(); - successfullyParsed = false; - return successfullyParsed; + return false; } @@ -431,8 +427,7 @@ Example cmdlet here var psScriptMissingVersionError = new ErrorRecord(ex, "psScriptMissingVersion", ErrorCategory.ParserError, null); errorsList.Add(psScriptMissingVersionError); errors = errorsList.ToArray(); - successfullyParsed = false; - return successfullyParsed; + return false; } if (!parsedPSScriptInfoHashtable.ContainsKey("AUTHOR") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["AUTHOR"])) @@ -442,8 +437,7 @@ Example cmdlet here var psScriptMissingAuthorError = new ErrorRecord(ex, "psScriptMissingAuthor", ErrorCategory.ParserError, null); errorsList.Add(psScriptMissingAuthorError); errors = errorsList.ToArray(); - successfullyParsed = false; - return successfullyParsed; + return false;; } if (!parsedPSScriptInfoHashtable.ContainsKey("GUID") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["GUID"])) @@ -453,8 +447,7 @@ Example cmdlet here var psScriptMissingGuidError = new ErrorRecord(ex, "psScriptMissingGuid", ErrorCategory.ParserError, null); errorsList.Add(psScriptMissingGuidError); errors = errorsList.ToArray(); - successfullyParsed = false; - return successfullyParsed; + return false; } string parsedVersion = (string) parsedPSScriptInfoHashtable["VERSION"]; @@ -525,11 +518,11 @@ Example cmdlet here var PSScriptFileInfoObjectNotCreatedFromFileError = new ErrorRecord(ex, "PSScriptFileInfoObjectNotCreatedFromFile", ErrorCategory.ParserError, null); errorsList.Add(PSScriptFileInfoObjectNotCreatedFromFileError); errors = errorsList.ToArray(); - successfullyParsed = false; + return false; } - return successfullyParsed; + return true; } /// From a175c6979e54592295c54db7b0f0313ef3317eef Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 13 Jun 2022 01:20:28 -0400 Subject: [PATCH 56/86] refactor TryParsePSScript() to use helpers --- src/code/PSScriptFileInfo.cs | 503 ++++++++++++++++++++++++++--- src/code/UpdatePSScriptFileInfo.cs | 2 + 2 files changed, 459 insertions(+), 46 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 43c328334..70b60a217 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -205,6 +205,7 @@ string description #endregion #region Public Methods + public string[] GetFileContents() { if (fileContents == null || fileContents.Length == 0) @@ -224,6 +225,385 @@ public void SetFileContents(string[] fileUpdatedContents) #region Internal Static Methods + // one method that takes .ps1 file and creates hashtable + internal static bool TryParseScript( + string scriptFileInfoPath, + out Hashtable parsedScriptMetadata, + out string[] endOfFileContents, + out ErrorRecord[] errors + ) + { + errors = new ErrorRecord[]{}; + parsedScriptMetadata = new Hashtable(); + endOfFileContents = new string[]{}; + 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 ScriptBlockAst.ScriptRequirements.RequiredModules + // ex of ScriptRequirements + // ex of RequiredModules + // TODO: (Anam) in comment include ex of ScriptRequirements and RequiredModules (above) + 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)).First(); + 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; + } + + // TODO: discuss with Paul, psScriptInfoCommentToken.text looks like this, why match to beginning of line? + // that would get each line that matches <#PSScriptInfo at the start, which is only line 1 + /** + <#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"}; // TODO should I also have \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 + 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; + } + + for (int i = 1; i < commentLines.Count(); i++) + { + string line = commentLines[i]; + + // A line is starting with . conveys a new metadata property + if (line.Trim().StartsWith(".")) + { + // .KEY VALUE + string[] parts = line.Trim().TrimStart('.').Split(); + keyName = parts[0]; + value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; + parsedScriptMetadata.Add(keyName, value); + } + } + + // get end of file contents + // TODO: (Anam) when updating write warning to user that the signature will be invalidated, needs to be regenerated + string[] totalFileContents = File.ReadAllLines(scriptFileInfoPath); + var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); + var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); + if (contentAfterDescription.Count() > 0) + { + endOfFileContents = contentAfterDescription.ToArray(); + } + + return true; + } + + internal 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 + // TODO: I think hashtable keys should be lower cased + + 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); + errors = errorsList.ToArray(); + return false; + } + + 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); + errors = errorsList.ToArray(); + return false;; + } + + 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 false; + } + + errors = errorsList.ToArray(); + return true; + } + + 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 (!TryParseScript( + scriptFileInfoPath: scriptFileInfoPath, + parsedScriptMetadata: out Hashtable parsedScriptMetadata, + endOfFileContents: out string[] endofFileContents, + errors: out ErrorRecord[] parseErrors + )) + { + errorsList.AddRange(parseErrors); + errors = errorsList.ToArray(); + return false; + } + + // 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 parsedCopyright = (string) parsedScriptMetadata["COPYRIGHT"]; + string parsedPrivateData = (string) parsedScriptMetadata["PRIVATEDATA"]; // TODO: fix PrivateData bug? or already fixed? + + 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"]); + string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["RELEASENOTES"]); + ReadOnlyCollection parsedModules = (ReadOnlyCollection) parsedScriptMetadata["REQUIREDMODULES"]; // TODO: does this work? + Uri parsedLicenseUri = null; + Uri parsedProjectUri = null; + Uri parsedIconUri = null; + + if (!String.IsNullOrEmpty((string) parsedScriptMetadata["LICENSEURI"])) + { + if (!Uri.TryCreate((string) parsedScriptMetadata["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri)) + { + verboseMsgsList.Add("LicenseUri property with value: '{0}' could not be created as a Uri"); + } + } + + if (!String.IsNullOrEmpty((string) parsedScriptMetadata["PROJECTURI"])) + { + if (!Uri.TryCreate((string) parsedScriptMetadata["PROJECTURI"], UriKind.Absolute, out parsedProjectUri)) + { + verboseMsgsList.Add("ProjectUri property with value: '{1} could not be created as Uri"); + } + } + + if (!String.IsNullOrEmpty((string) parsedScriptMetadata["ICONURI"])) + { + if (!Uri.TryCreate((string) parsedScriptMetadata["ICONURI"], UriKind.Absolute, out parsedIconUri)) + { + verboseMsgsList.Add("ProjectUri property with value: '{1} 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: parsedModules.ToArray(), + externalModuleDependencies: parsedExternalModuleDependencies, + requiredScripts: parsedRequiredScripts, + externalScriptDependencies: parsedExternalScriptDependencies, + releaseNotes: parsedReleaseNotes, + privateData: parsedPrivateData, + description: parsedDescription); + + // get file contents and set member of PSScriptFileInfo instance + if (endofFileContents != null && endofFileContents.Length > 0) + { + parsedScript.SetFileContents(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; + } + /// /// Tests the contents of the .ps1 file at the provided path /// @@ -253,6 +633,7 @@ .PARAMETER Name Example cmdlet here #> + */ parsedScript = null; @@ -300,9 +681,10 @@ Example cmdlet here if (ast == null) { - var astNullInnerEx = new ParseException("Parsed AST was null for .ps1 file"); - var parseFileEx = new InvalidOperationException("Cannot parse .ps1 file", astNullInnerEx); //TODO: nest this formatting wise with named args - var astCouldNotBeCreatedError = new ErrorRecord(parseFileEx, "ASTCouldNotBeCreated", ErrorCategory.ParserError, 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(); @@ -311,12 +693,15 @@ Example cmdlet here // Get the block/group comment beginning with <#PSScriptInfo Hashtable parsedPSScriptInfoHashtable = new Hashtable(); - List commentTokens = tokens.Where(a => String.Equals(a.Kind.ToString(), "Comment", StringComparison.OrdinalIgnoreCase)).ToList(); + // String.Equals(a.Kind.ToString(), "Comment" , StringComparison.OrdinalIgnoreCase) + // TODO: (Anam) Regex begin with, not anywhere exists + List commentTokens = tokens.Where(a => a.Kind == TokenKind.Comment).ToList(); string commentPattern = "<#PSScriptInfo"; Regex rg = new Regex(commentPattern); - List psScriptInfoCommentTokens = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).ToList(); - if (psScriptInfoCommentTokens.Count() == 0 || psScriptInfoCommentTokens[0] == null) + Token psScriptInfoCommentToken = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).First(); + + if (psScriptInfoCommentToken == null) { var message = String.Format("PSScriptInfo comment was missing or could not be parsed"); var ex = new ArgumentException(message); @@ -327,7 +712,43 @@ Example cmdlet here return false; } - string[] commentLines = Regex.Split(psScriptInfoCommentTokens[0].Text, "[\r\n]"); + /** + <#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"}; // TODO should I also have \n? + string[] commentLines = psScriptInfoCommentToken.Text.Split(newlineDelimeters, StringSplitOptions.RemoveEmptyEntries); string keyName = String.Empty; string value = String.Empty; @@ -339,28 +760,34 @@ Example cmdlet here #> */ - if (commentLines.Count() > 2) + if (commentLines.Count() <= 2) { - // TODO: is it an error if the aforementioned metadata property is empty? - for (int i = 1; i < commentLines.Count(); i++) - { - string line = commentLines[i]; - if (String.IsNullOrEmpty(line)) - { - continue; - } + 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); - // A line is starting with . conveys a new metadata property - if (line.Trim().StartsWith(".")) - { - string[] parts = line.Trim().TrimStart('.').Split(); - keyName = parts[0]; - value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; - parsedPSScriptInfoHashtable.Add(keyName, value); - } + errors = errorsList.ToArray(); + return false; + } + + for (int i = 1; i < commentLines.Count(); i++) + { + string line = commentLines[i]; + + // A line is starting with . conveys a new metadata property + if (line.Trim().StartsWith(".")) + { + // .KEY VALUE + string[] parts = line.Trim().TrimStart('.').Split(); + keyName = parts[0]; + value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; + parsedPSScriptInfoHashtable.Add(keyName, value); } } + + // TODO: move to top, low hanging fruit // get .DESCRIPTION comment CommentHelpInfo scriptCommentInfo = ast.GetHelpContent(); if (scriptCommentInfo == null) @@ -389,36 +816,16 @@ Example cmdlet here // get RequiredModules + // TODO: (Anam) in comment include ex of ScriptRequirements and RequiredModules (above) ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; ReadOnlyCollection parsedModules = new List().AsReadOnly(); if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) { parsedModules = parsedScriptRequirements.RequiredModules; - parsedPSScriptInfoHashtable.Add("RequiredModules", parsedModules); + parsedPSScriptInfoHashtable.Add("RequiredModules", parsedModules); // TODO rename parsedPSScriptInfoHashtable to parsedMEtadata } - // get all defined functions and populate DefinedCommands, DefinedFunctions, DefinedWorkflow - // DefinedWorkflow is no longer supported as of PowerShellCore 6+, but we ignore error - var parsedFunctionAst = ast.FindAll(a => a is FunctionDefinitionAst, true); - List allCommands = new List(); - if (allCommands.Count() > 0) - { - foreach (var function in parsedFunctionAst) - { - allCommands.Add((FunctionDefinitionAst) function); - } - - List allCommandNames = allCommands.Select(a => a.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedCommands", allCommandNames); - - // b.Body.Extent.Text to get actual content. So "Function b.Name c.Body.Extent.Text" is that whole line - List allFunctionNames = allCommands.Where(a => !a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedFunctions", allFunctionNames); - - List allWorkflowNames = allCommands.Where(a => a.IsWorkflow).Select(b => b.Name).Distinct().ToList(); - parsedPSScriptInfoHashtable.Add("DefinedWorkflows", allWorkflowNames); - } if (!parsedPSScriptInfoHashtable.ContainsKey("VERSION") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["VERSION"])) { @@ -454,6 +861,7 @@ Example cmdlet here string parsedAuthor = (string) parsedPSScriptInfoHashtable["AUTHOR"]; Guid parsedGuid = new Guid((string) parsedPSScriptInfoHashtable["GUID"]); + // TODO: this should all be in a validation method try { @@ -470,6 +878,7 @@ Example cmdlet here if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["LICENSEURI"])) { Uri.TryCreate((string) parsedPSScriptInfoHashtable["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri); + // TODO: verbose if false } if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["PROJECTURI"])) @@ -502,6 +911,7 @@ Example cmdlet here description: scriptCommentInfo.Description); // get file contents and set member of PSScriptFileInfo instance + // TODO: (Anam) when updating write warning to user that the signature will be invalidated, needs to be regenerated string[] totalFileContents = File.ReadAllLines(scriptFileInfoPath); var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); @@ -959,5 +1369,6 @@ public bool StringContainsComment(string stringToValidate) } #endregion + } } \ No newline at end of file diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 3cd29013f..4f99579c5 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -301,6 +301,8 @@ protected override void ProcessRecord() fs.Write(info, 0, info.Length); } + // TODO: update should do validation when it updates + // perhaps remove Validate, look in V2 if (Validate) { if (!PSScriptFileInfo.TryParseScriptFile( From 4cd6fdd1b7ec599b43395c90f59bae3e41d7d6a0 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 13 Jun 2022 02:25:31 -0400 Subject: [PATCH 57/86] fix bug with RequiredModules --- src/code/PSScriptFileInfo.cs | 7 +++++-- src/code/TestPSScriptFileInfo.cs | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 70b60a217..70fecddd9 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -536,7 +536,10 @@ internal static bool TryParseScriptIntoPSScriptInfo( string[] parsedRequiredScripts = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["REQUIREDSCRIPTS"]); string[] parsedExternalScriptDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["EXTERNALSCRIPTDEPENDENCIES"]); string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["RELEASENOTES"]); - ReadOnlyCollection parsedModules = (ReadOnlyCollection) parsedScriptMetadata["REQUIREDMODULES"]; // TODO: does this work? + + ReadOnlyCollection parsedModules = parsedScriptMetadata["REQUIREDMODULES"] == null ? new ReadOnlyCollection(new List()) : (ReadOnlyCollection) parsedScriptMetadata["REQUIREDMODULES"]; // TODO: does this work? + ModuleSpecification[] parsedModulesArray = parsedModules.Count == 0 ? new ModuleSpecification[]{} : parsedModules.ToArray(); + Uri parsedLicenseUri = null; Uri parsedProjectUri = null; Uri parsedIconUri = null; @@ -576,7 +579,7 @@ internal static bool TryParseScriptIntoPSScriptInfo( licenseUri: parsedLicenseUri, projectUri: parsedProjectUri, iconUri: parsedIconUri, - requiredModules: parsedModules.ToArray(), + requiredModules: parsedModulesArray, externalModuleDependencies: parsedExternalModuleDependencies, requiredScripts: parsedRequiredScripts, externalScriptDependencies: parsedExternalScriptDependencies, diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index ec8f6fdf7..90aa2a605 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -65,10 +65,16 @@ protected override void ProcessRecord() ThrowTerminatingError(FileDoesNotExistError); } - bool isValidScript = PSScriptFileInfo.TryParseScriptFile( + // bool isValidScript = PSScriptFileInfo.TryParseScriptFile( + // scriptFileInfoPath: resolvedFilePath, + // parsedScript: out PSScriptFileInfo parsedScriptInfo, + // errors: out ErrorRecord[] errors); + + bool isValidScript = PSScriptFileInfo.TryParseScriptIntoPSScriptInfo( scriptFileInfoPath: resolvedFilePath, parsedScript: out PSScriptFileInfo parsedScriptInfo, - errors: out ErrorRecord[] errors); + errors: out ErrorRecord[] errors, + out string[] verboseMsgs); if (!isValidScript) { @@ -78,6 +84,11 @@ protected override void ProcessRecord() } } + foreach (string msg in verboseMsgs) + { + WriteVerbose(msg); + } + WriteObject(isValidScript); } From d7a9d5b16ef70bc49c402d9adf375007f49f541a Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 13 Jun 2022 03:23:44 -0400 Subject: [PATCH 58/86] cmdlets should use new refactored TryParseScriptIntoPSScriptInfo method --- src/code/PSScriptFileInfo.cs | 344 +---------------------------- src/code/TestPSScriptFileInfo.cs | 5 - src/code/UpdatePSScriptFileInfo.cs | 29 ++- 3 files changed, 31 insertions(+), 347 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 70fecddd9..42fe5629c 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -226,6 +226,9 @@ public void SetFileContents(string[] fileUpdatedContents) #region Internal Static Methods // one method that takes .ps1 file and creates hashtable + /// + /// Parses content of .ps1 file into a hashtable + /// internal static bool TryParseScript( string scriptFileInfoPath, out Hashtable parsedScriptMetadata, @@ -374,7 +377,7 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd #> */ - string[] newlineDelimeters = new string[]{"\r\n"}; // TODO should I also have \n? + string[] newlineDelimeters = new string[]{"\r\n", "\n"}; // TODO should I also have \n? string[] commentLines = psScriptInfoCommentToken.Text.Split(newlineDelimeters, StringSplitOptions.RemoveEmptyEntries); string keyName = String.Empty; string value = String.Empty; @@ -426,6 +429,9 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd return true; } + /// + /// Takes hashtable (containing parsed .ps1 file content properties) and validates required properties are present + /// internal static bool TryValidateScript( Hashtable parsedScriptMetadata, out ErrorRecord[] errors @@ -471,6 +477,9 @@ out ErrorRecord[] errors return true; } + /// + /// Tests the contents of the .ps1 file at the provided path + /// internal static bool TryParseScriptIntoPSScriptInfo( string scriptFileInfoPath, out PSScriptFileInfo parsedScript, @@ -538,7 +547,7 @@ internal static bool TryParseScriptIntoPSScriptInfo( string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["RELEASENOTES"]); ReadOnlyCollection parsedModules = parsedScriptMetadata["REQUIREDMODULES"] == null ? new ReadOnlyCollection(new List()) : (ReadOnlyCollection) parsedScriptMetadata["REQUIREDMODULES"]; // TODO: does this work? - ModuleSpecification[] parsedModulesArray = parsedModules.Count == 0 ? new ModuleSpecification[]{} : parsedModules.ToArray(); + ModuleSpecification[] parsedModulesArray = parsedModules.Count() == 0 ? new ModuleSpecification[]{} : parsedModules.ToArray(); Uri parsedLicenseUri = null; Uri parsedProjectUri = null; @@ -607,337 +616,6 @@ internal static bool TryParseScriptIntoPSScriptInfo( return true; } - /// - /// Tests the contents of the .ps1 file at the provided path - /// - internal static bool TryParseScriptFile( - string scriptFileInfoPath, - out PSScriptFileInfo parsedScript, - out ErrorRecord[] errors) - { - // a valid example script will have this format: - /* <#PSScriptInfo - .VERSION 1.6 - .GUID abf490023 - 9128 - 4323 - sdf9a - jf209888ajkl - .AUTHOR Jane Doe - .COMPANYNAME Microsoft - .COPYRIGHT - .TAGS Windows MacOS - #> - - <# - - .SYNOPSIS - Synopsis description here - .DESCRIPTION - Description here - .PARAMETER Name - .EXAMPLE - Example cmdlet here - - #> - - */ - - parsedScript = null; - errors = new ErrorRecord[]{}; - List errorsList = 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; - } - - // 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 the block/group comment beginning with <#PSScriptInfo - Hashtable parsedPSScriptInfoHashtable = new Hashtable(); - // String.Equals(a.Kind.ToString(), "Comment" , StringComparison.OrdinalIgnoreCase) - // TODO: (Anam) Regex begin with, not anywhere exists - 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)).First(); - - 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; - } - - /** - <#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"}; // TODO should I also have \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 - 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; - } - - for (int i = 1; i < commentLines.Count(); i++) - { - string line = commentLines[i]; - - // A line is starting with . conveys a new metadata property - if (line.Trim().StartsWith(".")) - { - // .KEY VALUE - string[] parts = line.Trim().TrimStart('.').Split(); - keyName = parts[0]; - value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty; - parsedPSScriptInfoHashtable.Add(keyName, value); - } - } - - - // TODO: move to top, low hanging fruit - // get .DESCRIPTION comment - 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("#>")) - { - parsedPSScriptInfoHashtable.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 - // TODO: (Anam) in comment include ex of ScriptRequirements and RequiredModules (above) - ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; - ReadOnlyCollection parsedModules = new List().AsReadOnly(); - - if (parsedScriptRequirements != null && parsedScriptRequirements.RequiredModules != null) - { - parsedModules = parsedScriptRequirements.RequiredModules; - parsedPSScriptInfoHashtable.Add("RequiredModules", parsedModules); // TODO rename parsedPSScriptInfoHashtable to parsedMEtadata - } - - - if (!parsedPSScriptInfoHashtable.ContainsKey("VERSION") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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); - errors = errorsList.ToArray(); - return false; - } - - if (!parsedPSScriptInfoHashtable.ContainsKey("AUTHOR") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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); - errors = errorsList.ToArray(); - return false;; - } - - if (!parsedPSScriptInfoHashtable.ContainsKey("GUID") || String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["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 false; - } - - string parsedVersion = (string) parsedPSScriptInfoHashtable["VERSION"]; - string parsedAuthor = (string) parsedPSScriptInfoHashtable["AUTHOR"]; - Guid parsedGuid = new Guid((string) parsedPSScriptInfoHashtable["GUID"]); - - // TODO: this should all be in a validation method - - try - { - char[] spaceDelimeter = new char[]{' '}; - string[] parsedTags = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["TAGS"]); - string[] parsedExternalModuleDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["EXTERNALMODULEDEPENDENCIES"]); - string[] parsedRequiredScripts = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["REQUIREDSCRIPTS"]); - string[] parsedExternalScriptDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["EXTERNALSCRIPTDEPENDENCIES"]); - string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedPSScriptInfoHashtable["RELEASENOTES"]); - Uri parsedLicenseUri = null; - Uri parsedProjectUri = null; - Uri parsedIconUri = null; - - if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["LICENSEURI"])) - { - Uri.TryCreate((string) parsedPSScriptInfoHashtable["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri); - // TODO: verbose if false - } - - if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["PROJECTURI"])) - { - Uri.TryCreate((string) parsedPSScriptInfoHashtable["PROJECTURI"], UriKind.Absolute, out parsedProjectUri); - } - - if (!String.IsNullOrEmpty((string) parsedPSScriptInfoHashtable["ICONURI"])) - { - Uri.TryCreate((string) parsedPSScriptInfoHashtable["ICONURI"], UriKind.Absolute, out parsedProjectUri); - } - - // parsedPSScriptInfoHashtable 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: (string) parsedPSScriptInfoHashtable["COMPANYNAME"], - copyright: (string) parsedPSScriptInfoHashtable["COPYRIGHT"], - tags: parsedTags, - licenseUri: parsedLicenseUri, - projectUri: parsedProjectUri, - iconUri: parsedIconUri, - requiredModules: parsedModules.ToArray(), - externalModuleDependencies: parsedExternalModuleDependencies, - requiredScripts: parsedRequiredScripts, - externalScriptDependencies: parsedExternalScriptDependencies, - releaseNotes: parsedReleaseNotes, - privateData: (string) parsedPSScriptInfoHashtable["PRIVATEDATA"], - description: scriptCommentInfo.Description); - - // get file contents and set member of PSScriptFileInfo instance - // TODO: (Anam) when updating write warning to user that the signature will be invalidated, needs to be regenerated - string[] totalFileContents = File.ReadAllLines(scriptFileInfoPath); - var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); - var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); - if (contentAfterDescription.Count() > 0) - { - parsedScript.SetFileContents(contentAfterDescription.ToArray()); - } - - } - 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 diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 90aa2a605..31dae9c43 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -65,11 +65,6 @@ protected override void ProcessRecord() ThrowTerminatingError(FileDoesNotExistError); } - // bool isValidScript = PSScriptFileInfo.TryParseScriptFile( - // scriptFileInfoPath: resolvedFilePath, - // parsedScript: out PSScriptFileInfo parsedScriptInfo, - // errors: out ErrorRecord[] errors); - bool isValidScript = PSScriptFileInfo.TryParseScriptIntoPSScriptInfo( scriptFileInfoPath: resolvedFilePath, parsedScript: out PSScriptFileInfo parsedScriptInfo, diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 4f99579c5..343ae6f27 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -241,24 +241,29 @@ protected override void ProcessRecord() } } - // get PSScriptFileInfo object for current script contents - if (!PSScriptFileInfo.TryParseScriptFile( + if (!PSScriptFileInfo.TryParseScriptIntoPSScriptInfo( scriptFileInfoPath: resolvedFilePath, - out PSScriptFileInfo parsedScriptFileInfo, - out ErrorRecord[] errors)) + parsedScript: out PSScriptFileInfo parsedScriptInfo, + errors: out ErrorRecord[] errors, + out string[] verboseMsgs)) { + foreach (string msg in verboseMsgs) + { + WriteVerbose(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; // TODO: should this be a terminating error instead? + return; } else { if (!PSScriptFileInfo.TryUpdateScriptFile( - originalScript: ref parsedScriptFileInfo, + originalScript: ref parsedScriptInfo, updatedPSScriptFileContents: out string updatedPSScriptFileContents, // filePath: resolvedFilePath, errors: out ErrorRecord[] updateErrors, @@ -305,11 +310,17 @@ protected override void ProcessRecord() // perhaps remove Validate, look in V2 if (Validate) { - if (!PSScriptFileInfo.TryParseScriptFile( + if (!PSScriptFileInfo.TryParseScriptIntoPSScriptInfo( scriptFileInfoPath: tempScriptFilePath, - out parsedScriptFileInfo, - out ErrorRecord[] testErrors)) + out parsedScriptInfo, + out ErrorRecord[] testErrors, + out string[] verboseValidationMsgs)) { + foreach (string validationMsg in verboseValidationMsgs) + { + WriteVerbose(validationMsg); + } + WriteWarning("Validating the updated script file failed due to the following error(s):"); foreach (ErrorRecord error in testErrors) { From 529b532239d67f5669876245a6fee552bb07a941 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 14 Jun 2022 16:55:59 -0400 Subject: [PATCH 59/86] make certain methods private --- src/code/NewPSScriptFileInfo.cs | 16 ++-------------- src/code/PSScriptFileInfo.cs | 26 +++++++------------------- src/code/TestPSScriptFileInfo.cs | 4 ++-- src/code/UpdatePSScriptFileInfo.cs | 4 +--- 4 files changed, 12 insertions(+), 38 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 88a14bb61..1e413e7da 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -14,8 +14,7 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { /// - /// It retrieves a resource that was installed with Install-PSResource - /// Returns a single resource or multiple resource. + /// Creates a new .ps1 file with script information required for publishing a script /// [Cmdlet(VerbsCommon.New, "PSScriptFileInfo")] public sealed class NewPSScriptFileInfo : PSCmdlet @@ -208,17 +207,6 @@ protected override void ProcessRecord() ThrowTerminatingError(ScriptAtPathAlreadyExistsError); } - // 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; - ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; if (RequiredModules != null && RequiredModules.Length > 0) { @@ -254,7 +242,6 @@ protected override void ProcessRecord() description: Description); if (!currentScriptInfo.TryCreateScriptFileInfoString( - // filePath: resolvedFilePath, pSScriptFileString: out string psScriptFileContents, errors: out ErrorRecord[] errors)) { @@ -266,6 +253,7 @@ protected override void ProcessRecord() return; } + // File.Create handles relative and absolute paths using(FileStream fs = File.Create(FilePath)) { byte[] info = new UTF8Encoding(true).GetBytes(psScriptFileContents); diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 42fe5629c..b15271b0a 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -623,7 +623,6 @@ internal static bool TryParseScriptIntoPSScriptInfo( public static bool TryUpdateScriptFile( ref PSScriptFileInfo originalScript, out string updatedPSScriptFileContents, - // string filePath, out ErrorRecord[] errors, string version, Guid guid, @@ -758,7 +757,6 @@ public static bool TryUpdateScriptFile( // create string contents for .ps1 file if (!updatedScript.TryCreateScriptFileInfoString( pSScriptFileString: out string psScriptFileContents, - // filePath: filePath, errors: out ErrorRecord[] createFileContentErrors)) { errorsList.AddRange(createFileContentErrors); @@ -842,28 +840,18 @@ out ErrorRecord[] errors // or else not recongnized as a valid comment help info block when parsing the created ps1 later pSScriptFileString += "\n" + psHelpInfo; - // GetEndOfFileLinesContent2( - - // ) - - - // GetEndOfFileLinesContent( - // filePath: filePath, - // endOfFileContent: out string endOfFileAstContent); - // if (!String.IsNullOrEmpty(endOfFileAstContent)) - // { - // pSScriptFileString += "\n" + endOfFileAstContent; - // } - fileContentsSuccessfullyCreated = true; return fileContentsSuccessfullyCreated; } + #endregion + + #region Private Methods /// /// Used when creating .ps1 file's contents. /// This creates the <#PSScriptInfo ... #> comment string /// - public bool GetPSScriptInfoString( + private bool GetPSScriptInfoString( out string pSScriptInfoString, out ErrorRecord error ) @@ -934,7 +922,7 @@ Feature 5 /// Used when creating .ps1 file's contents. /// This creates the #Requires comment string /// - public void GetRequiresString( + private void GetRequiresString( out string psRequiresString ) { @@ -959,7 +947,7 @@ out string psRequiresString /// Used when creating .ps1 file's contents. /// This creates the help comment string: <# \n .DESCRIPTION #> /// - public bool GetScriptCommentHelpInfo( + private bool GetScriptCommentHelpInfo( out string psHelpInfo, out ErrorRecord error ) @@ -1044,7 +1032,7 @@ out ErrorRecord error /// /// Ensure no fields (passed as stringToValidate) contains '<#' or '#>' (would break comment section) /// - public bool StringContainsComment(string stringToValidate) + private bool StringContainsComment(string stringToValidate) { return stringToValidate.Contains("<#") || stringToValidate.Contains("#>"); } diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 31dae9c43..25c277003 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -16,8 +16,8 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { /// - /// It retrieves a resource that was installed with Install-PSResource - /// Returns a single resource or multiple resource. + /// 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))] diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 343ae6f27..bae37421c 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -14,8 +14,7 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { /// - /// It retrieves a resource that was installed with Install-PSResource - /// Returns a single resource or multiple resource. + /// Updates a .ps1 file with specified properties /// [Cmdlet(VerbsData.Update, "PSScriptFileInfo")] public sealed class UpdatePSScriptFileInfo : PSCmdlet @@ -265,7 +264,6 @@ protected override void ProcessRecord() if (!PSScriptFileInfo.TryUpdateScriptFile( originalScript: ref parsedScriptInfo, updatedPSScriptFileContents: out string updatedPSScriptFileContents, - // filePath: resolvedFilePath, errors: out ErrorRecord[] updateErrors, version: Version, guid: Guid, From f1cad374ba9156f5b1107e5e84903ee8254456b2 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 14 Jun 2022 16:58:43 -0400 Subject: [PATCH 60/86] make method internal for update --- src/code/PSScriptFileInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index b15271b0a..06bc268f5 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -620,7 +620,7 @@ internal static bool TryParseScriptIntoPSScriptInfo( /// 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 /// - public static bool TryUpdateScriptFile( + internal static bool TryUpdateScriptFile( ref PSScriptFileInfo originalScript, out string updatedPSScriptFileContents, out ErrorRecord[] errors, From 98ecc519622de883dfb109e5b52aef04696d5dd2 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 14 Jun 2022 18:26:42 -0400 Subject: [PATCH 61/86] code review changes --- src/code/PSScriptFileInfo.cs | 253 +++++++++++++---------------- src/code/UpdatePSScriptFileInfo.cs | 126 ++++++-------- 2 files changed, 162 insertions(+), 217 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 06bc268f5..05544e72c 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -110,6 +110,11 @@ public sealed class PSScriptFileInfo /// public string Description { get; set; } + /// + /// End of file contents for the .ps1 file + /// + public string[] EndOfFileContents { get; set; } = new string[]{}; + /// /// The synopsis of the script /// @@ -176,7 +181,8 @@ public PSScriptFileInfo( string[] externalScriptDependencies, string[] releaseNotes, string privateData, - string description + string description, + string[] endOfFileContents ) { if (String.IsNullOrEmpty(author)) @@ -200,25 +206,7 @@ string description ReleaseNotes = releaseNotes ?? Utils.EmptyStrArray; PrivateData = privateData; Description = description; - } - - #endregion - - #region Public Methods - - public string[] GetFileContents() - { - if (fileContents == null || fileContents.Length == 0) - { - fileContents = Utils.EmptyStrArray; - } - - return fileContents; - } - - public void SetFileContents(string[] fileUpdatedContents) - { - fileContents = fileUpdatedContents; + EndOfFileContents = endOfFileContents; } #endregion @@ -328,7 +316,7 @@ out ErrorRecord[] errors string commentPattern = "<#PSScriptInfo"; Regex rg = new Regex(commentPattern); - Token psScriptInfoCommentToken = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).First(); + 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"); @@ -340,7 +328,7 @@ out ErrorRecord[] errors return false; } - // TODO: discuss with Paul, psScriptInfoCommentToken.text looks like this, why match to beginning of line? + // TODO: add ^ // that would get each line that matches <#PSScriptInfo at the start, which is only line 1 /** <#PSScriptInfo @@ -388,6 +376,7 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd <#PSScriptInfo #> + */ if (commentLines.Count() <= 2) @@ -437,6 +426,8 @@ internal static bool TryValidateScript( out ErrorRecord[] errors ) { + // TODO: retuan all errors at once, wait til end + // required properties for script file (.ps1 file) are: Author, Version, Guid, Description // Description gets validated in TryParseScript() when getting the property // TODO: I think hashtable keys should be lower cased @@ -536,9 +527,11 @@ internal static bool TryParseScriptIntoPSScriptInfo( string parsedVersion = (string) parsedScriptMetadata["VERSION"]; string parsedAuthor = (string) parsedScriptMetadata["AUTHOR"]; string parsedDescription = (string) parsedScriptMetadata["DESCRIPTION"]; - string parsedCompanyName = (string) parsedScriptMetadata["COMPANYNAME"]; - string parsedCopyright = (string) parsedScriptMetadata["COPYRIGHT"]; - string parsedPrivateData = (string) parsedScriptMetadata["PRIVATEDATA"]; // TODO: fix PrivateData bug? or already fixed? + + + string parsedCompanyName = (string) parsedScriptMetadata["COMPANYNAME"] ?? String.Empty; + string parsedCopyright = (string) parsedScriptMetadata["COPYRIGHT"] ?? String.Empty; + string parsedPrivateData = (string) parsedScriptMetadata["PRIVATEDATA"] ?? String.Empty; // TODO: fix PrivateData bug? or already fixed? string[] parsedTags = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["TAGS"]); string[] parsedExternalModuleDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["EXTERNALMODULEDEPENDENCIES"]); @@ -546,36 +539,37 @@ internal static bool TryParseScriptIntoPSScriptInfo( string[] parsedExternalScriptDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["EXTERNALSCRIPTDEPENDENCIES"]); string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["RELEASENOTES"]); - ReadOnlyCollection parsedModules = parsedScriptMetadata["REQUIREDMODULES"] == null ? new ReadOnlyCollection(new List()) : (ReadOnlyCollection) parsedScriptMetadata["REQUIREDMODULES"]; // TODO: does this work? + ReadOnlyCollection parsedModules = (ReadOnlyCollection) parsedScriptMetadata["REQUIREDMODULES"] ?? + new ReadOnlyCollection(new List()); + ModuleSpecification[] parsedModulesArray = parsedModules.Count() == 0 ? new ModuleSpecification[]{} : parsedModules.ToArray(); Uri parsedLicenseUri = null; - Uri parsedProjectUri = null; - Uri parsedIconUri = null; - - if (!String.IsNullOrEmpty((string) parsedScriptMetadata["LICENSEURI"])) - { - if (!Uri.TryCreate((string) parsedScriptMetadata["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri)) + if (!String.IsNullOrEmpty((string) parsedScriptMetadata["LICENSEURI"])) { - verboseMsgsList.Add("LicenseUri property with value: '{0}' could not be created as a Uri"); + if (!Uri.TryCreate((string) parsedScriptMetadata["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri)) + { + verboseMsgsList.Add("LicenseUri property could not be created as a Uri"); + } } - } - if (!String.IsNullOrEmpty((string) parsedScriptMetadata["PROJECTURI"])) - { - if (!Uri.TryCreate((string) parsedScriptMetadata["PROJECTURI"], UriKind.Absolute, out parsedProjectUri)) + Uri parsedProjectUri = null; + if (!String.IsNullOrEmpty((string) parsedScriptMetadata["PROJECTURI"])) { - verboseMsgsList.Add("ProjectUri property with value: '{1} could not be created as Uri"); + if (!Uri.TryCreate((string) parsedScriptMetadata["PROJECTURI"], UriKind.Absolute, out parsedProjectUri)) + { + verboseMsgsList.Add("ProjectUri property could not be created as Uri"); + } } - } - if (!String.IsNullOrEmpty((string) parsedScriptMetadata["ICONURI"])) - { - if (!Uri.TryCreate((string) parsedScriptMetadata["ICONURI"], UriKind.Absolute, out parsedIconUri)) + Uri parsedIconUri = null; + if (!String.IsNullOrEmpty((string) parsedScriptMetadata["ICONURI"])) { - verboseMsgsList.Add("ProjectUri property with value: '{1} could not be created as Uri"); + if (!Uri.TryCreate((string) parsedScriptMetadata["ICONURI"], UriKind.Absolute, out parsedIconUri)) + { + verboseMsgsList.Add("IconUri property 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( @@ -594,14 +588,8 @@ internal static bool TryParseScriptIntoPSScriptInfo( externalScriptDependencies: parsedExternalScriptDependencies, releaseNotes: parsedReleaseNotes, privateData: parsedPrivateData, - description: parsedDescription); - - // get file contents and set member of PSScriptFileInfo instance - if (endofFileContents != null && endofFileContents.Length > 0) - { - parsedScript.SetFileContents(endofFileContents); - } - + description: parsedDescription, + endOfFileContents: endofFileContents); } catch (Exception e) { @@ -620,8 +608,8 @@ internal static bool TryParseScriptIntoPSScriptInfo( /// 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 TryUpdateScriptFile( - ref PSScriptFileInfo originalScript, + internal static bool TryUpdateScriptFileContents( + PSScriptFileInfo scriptInfo, out string updatedPSScriptFileContents, out ErrorRecord[] errors, string version, @@ -641,137 +629,114 @@ internal static bool TryUpdateScriptFile( string privateData, string description) { - bool successfullyUpdated = false; updatedPSScriptFileContents = String.Empty; errors = new ErrorRecord[]{}; List errorsList = new List(); - if (originalScript == null) + if (scriptInfo == null) { - var message = String.Format("PSScriptFileInfo object to update is null."); - var ex = new ArgumentException(message); - var nullPSScriptFileInfoObjectToUpdateError = new ErrorRecord(ex, "NullPSScriptFileInfoObjectToUpdate", ErrorCategory.ParserError, null); - errorsList.Add(nullPSScriptFileInfoObjectToUpdateError); - errors = errorsList.ToArray(); - return successfullyUpdated; + throw new ArgumentNullException(nameof(scriptInfo)); } - - PSScriptFileInfo updatedScript = originalScript; // create new PSScriptFileInfo with updated fields - try + if (!String.IsNullOrEmpty(version)) { - if (!String.IsNullOrEmpty(version)) + if (!NuGetVersion.TryParse(version, out NuGetVersion updatedVersion)) { - 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 successfullyUpdated; - } - updatedScript.Version = updatedVersion; - } - - if (guid != Guid.Empty) - { - updatedScript.Guid = guid; - } - - if (!String.IsNullOrEmpty(author)) - { - updatedScript.Author = author; - } - - if (!String.IsNullOrEmpty(companyName)){ - updatedScript.CompanyName = companyName; + 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 (!String.IsNullOrEmpty(copyright)){ - updatedScript.Copyright = copyright; - } + if (guid != Guid.Empty) + { + scriptInfo.Guid = guid; + } - if (tags != null && tags.Length != 0){ - updatedScript.Tags = tags; - } + if (!String.IsNullOrEmpty(author)) + { + scriptInfo.Author = author; + } - if (licenseUri != null && !licenseUri.Equals(default(Uri))){ - updatedScript.LicenseUri = licenseUri; - } + if (!String.IsNullOrEmpty(companyName)){ + scriptInfo.CompanyName = companyName; + } - if (projectUri != null && !projectUri.Equals(default(Uri))){ - updatedScript.ProjectUri = projectUri; - } + if (!String.IsNullOrEmpty(copyright)){ + scriptInfo.Copyright = copyright; + } - if (iconUri != null && !iconUri.Equals(default(Uri))){ - updatedScript.IconUri = iconUri; - } + if (tags != null && tags.Length != 0){ + scriptInfo.Tags = tags; + } - if (requiredModules != null && requiredModules.Length != 0){ - updatedScript.RequiredModules = requiredModules; - } + if (licenseUri != null && !licenseUri.Equals(default(Uri))){ + scriptInfo.LicenseUri = licenseUri; + } - if (externalModuleDependencies != null && externalModuleDependencies.Length != 0){ - updatedScript.ExternalModuleDependencies = externalModuleDependencies; - } + if (projectUri != null && !projectUri.Equals(default(Uri))){ + scriptInfo.ProjectUri = projectUri; + } - if (requiredScripts != null && requiredScripts.Length != 0) - { - updatedScript.RequiredScripts = requiredScripts; - } + if (iconUri != null && !iconUri.Equals(default(Uri))){ + scriptInfo.IconUri = iconUri; + } - if (externalScriptDependencies != null && externalScriptDependencies.Length != 0){ - updatedScript.ExternalScriptDependencies = externalScriptDependencies; - } + if (requiredModules != null && requiredModules.Length != 0){ + scriptInfo.RequiredModules = requiredModules; + } - if (releaseNotes != null && releaseNotes.Length != 0) - { - updatedScript.ReleaseNotes = releaseNotes; - } + if (externalModuleDependencies != null && externalModuleDependencies.Length != 0){ + scriptInfo.ExternalModuleDependencies = externalModuleDependencies; + } - if (!String.IsNullOrEmpty(privateData)) - { - updatedScript.PrivateData = privateData; - } + if (requiredScripts != null && requiredScripts.Length != 0) + { + scriptInfo.RequiredScripts = requiredScripts; + } - if (!String.IsNullOrEmpty(description)) - { - updatedScript.Description = description; - } + if (externalScriptDependencies != null && externalScriptDependencies.Length != 0){ + scriptInfo.ExternalScriptDependencies = externalScriptDependencies; + } - originalScript = updatedScript; + if (releaseNotes != null && releaseNotes.Length != 0) + { + scriptInfo.ReleaseNotes = releaseNotes; } - catch (Exception exception) + + if (!String.IsNullOrEmpty(privateData)) { - var message = String.Format(".ps1 file and associated PSScriptFileInfo object's field could not be updated due to {0}.", exception.Message); - var ex = new ArgumentException(message); - var PSScriptFileInfoFieldCouldNotBeUpdatedError = new ErrorRecord(ex, "PSScriptFileInfoFieldCouldNotBeUpdated", ErrorCategory.ParserError, null); - errorsList.Add(PSScriptFileInfoFieldCouldNotBeUpdatedError); - errors = errorsList.ToArray(); - return successfullyUpdated; + scriptInfo.PrivateData = privateData; } + if (!String.IsNullOrEmpty(description)) + { + scriptInfo.Description = description; + } // create string contents for .ps1 file - if (!updatedScript.TryCreateScriptFileInfoString( + if (!scriptInfo.TryCreateScriptFileInfoString( pSScriptFileString: out string psScriptFileContents, errors: out ErrorRecord[] createFileContentErrors)) { errorsList.AddRange(createFileContentErrors); errors = errorsList.ToArray(); - return successfullyUpdated; + return false; } - if (updatedScript.GetFileContents() != null && updatedScript.GetFileContents().Length > 0) + // TODO: move into TryCreateScriptFileInfoString, if it exists then add, if not you dont + if (scriptInfo.EndOfFileContents.Length > 0) { - psScriptFileContents += "\n" + String.Join("\n", updatedScript.GetFileContents()); + psScriptFileContents += "\n" + String.Join("\n", scriptInfo.EndOfFileContents); } - successfullyUpdated = true; updatedPSScriptFileContents = psScriptFileContents; - return successfullyUpdated; + return true; } #endregion diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index bae37421c..3f1c1f1f3 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -20,6 +20,7 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets public sealed class UpdatePSScriptFileInfo : PSCmdlet { #region Members + // TODO: make these variables private Uri _projectUri; private Uri _licenseUri; private Uri _iconUri; @@ -259,85 +260,64 @@ protected override void ProcessRecord() return; } - else + + 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)) { - if (!PSScriptFileInfo.TryUpdateScriptFile( - originalScript: ref 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) { - WriteWarning("Updating the specified script file failed due to the following error(s):"); - foreach (ErrorRecord error in updateErrors) - { - WriteError(error); - } + WriteError(error); } - else - { - // write string of file contents to a temp file - var tempScriptDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var tempScriptFilePath = Path.Combine(tempScriptDirPath, "tempScript.ps1"); - if (!Directory.Exists(tempScriptDirPath)) - { - Directory.CreateDirectory(tempScriptDirPath); - } - - using(FileStream fs = File.Create(tempScriptFilePath)) - { - byte[] info = new UTF8Encoding(true).GetBytes(updatedPSScriptFileContents); - fs.Write(info, 0, info.Length); - } - - // TODO: update should do validation when it updates - // perhaps remove Validate, look in V2 - if (Validate) - { - if (!PSScriptFileInfo.TryParseScriptIntoPSScriptInfo( - scriptFileInfoPath: tempScriptFilePath, - out parsedScriptInfo, - out ErrorRecord[] testErrors, - out string[] verboseValidationMsgs)) - { - foreach (string validationMsg in verboseValidationMsgs) - { - WriteVerbose(validationMsg); - } - - WriteWarning("Validating the updated script file failed due to the following error(s):"); - foreach (ErrorRecord error in testErrors) - { - WriteError(error); - } - - return; // TODO: if Validation was requested and fails, do we still write out updated file PSScriptFileInfo object? - } - } - File.Copy(tempScriptFilePath, resolvedFilePath, true); - Utils.DeleteDirectory(tempScriptDirPath); + return; + } + + // TODO: put this in a try catch + // write string of file contents to a temp file + string tempScriptFilePath = null; + try + { + tempScriptFilePath = Path.GetTempFileName(); - if (PassThru) - { - WriteObject(updatedPSScriptFileContents); - } + File.WriteAllText(tempScriptFilePath, updatedPSScriptFileContents); + File.Copy(tempScriptFilePath, resolvedFilePath, overwrite: true); + } + catch(Exception e) + { + // TODO + } + finally + { + if (tempScriptFilePath != null) + { + File.Delete(tempScriptFilePath); } - } + } + + // TODO: ceck with Sydney if we wnat to return PSScriptFileInfo obj? + if (PassThru) + { + WriteObject(updatedPSScriptFileContents); + } } #endregion From a61ba8790027512e797166684b2fadc4040872b8 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 22 Jun 2022 10:19:19 -0400 Subject: [PATCH 62/86] add RemoveSignature param to Update --- src/code/NewPSScriptFileInfo.cs | 50 ++++--------- src/code/PSScriptFileInfo.cs | 103 ++++++++++++-------------- src/code/UpdatePSScriptFileInfo.cs | 85 +++++++++++---------- src/code/Utils.cs | 20 +++-- test/NewPSScriptFileInfo.Tests.ps1 | 9 +-- test/UpdatePSScriptFileInfo.Tests.ps1 | 6 +- 6 files changed, 127 insertions(+), 146 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 1e413e7da..b70e9c938 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -19,13 +19,6 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets [Cmdlet(VerbsCommon.New, "PSScriptFileInfo")] public sealed class NewPSScriptFileInfo : PSCmdlet { - #region Members - private Uri _projectUri; - private Uri _licenseUri; - private Uri _iconUri; - - #endregion - #region Parameters /// @@ -147,12 +140,6 @@ public sealed class NewPSScriptFileInfo : PSCmdlet [ValidateNotNullOrEmpty()] public string PrivateData { get; set; } - /// - /// If specified, the .ps1 file contents are additionally written out to the console - /// - [Parameter] - public SwitchParameter PassThru { get; set; } - /// /// If used with Path parameter and .ps1 file specified at the path exists, it rewrites the file /// @@ -166,25 +153,28 @@ public sealed class NewPSScriptFileInfo : PSCmdlet protected override void ProcessRecord() { // validate Uri related parameters passed in as strings + Uri projectUri = null; if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri, cmdletPassedIn: this, - uriResult: out _projectUri, + 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, + 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, + uriResult: out iconUri, errorRecord: out ErrorRecord iconErrorRecord)) { ThrowTerminatingError(iconErrorRecord); @@ -210,16 +200,17 @@ protected override void ProcessRecord() ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; if (RequiredModules != null && RequiredModules.Length > 0) { - Utils.CreateModuleSpecification( + if (!Utils.TryCreateModuleSpecification( moduleSpecHashtables: RequiredModules, out validatedRequiredModuleSpecifications, - out ErrorRecord[] moduleSpecErrors); - if (moduleSpecErrors.Length > 0) + out ErrorRecord[] moduleSpecErrors)) { foreach (ErrorRecord err in moduleSpecErrors) { WriteError(err); } + + return; } } @@ -230,16 +221,17 @@ protected override void ProcessRecord() companyName: CompanyName, copyright: Copyright, tags: Tags, - licenseUri: _licenseUri, - projectUri: _projectUri, - iconUri: _iconUri, + licenseUri: licenseUri, + projectUri: projectUri, + iconUri: iconUri, requiredModules: validatedRequiredModuleSpecifications, externalModuleDependencies: ExternalModuleDependencies, requiredScripts: RequiredScripts, externalScriptDependencies: ExternalScriptDependencies, releaseNotes: ReleaseNotes, privateData: PrivateData, - description: Description); + description: Description, + endOfFileContents: String.Empty); if (!currentScriptInfo.TryCreateScriptFileInfoString( pSScriptFileString: out string psScriptFileContents, @@ -253,17 +245,7 @@ protected override void ProcessRecord() return; } - // File.Create handles relative and absolute paths - using(FileStream fs = File.Create(FilePath)) - { - byte[] info = new UTF8Encoding(true).GetBytes(psScriptFileContents); - fs.Write(info, 0, info.Length); - } - - if (PassThru) - { - WriteObject(psScriptFileContents); - } + File.WriteAllText(FilePath, psScriptFileContents); } #endregion diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 05544e72c..979add1bd 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -20,12 +20,7 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents) /// public sealed class PSScriptFileInfo - { - #region Members - - private string[] fileContents = new string[]{}; - - #endregion + { #region Properties @@ -109,11 +104,11 @@ public sealed class PSScriptFileInfo /// The description of the script /// public string Description { get; set; } - + /// /// End of file contents for the .ps1 file /// - public string[] EndOfFileContents { get; set; } = new string[]{}; + public string EndOfFileContents { get; set; } = String.Empty; /// /// The synopsis of the script @@ -162,6 +157,8 @@ public sealed class PSScriptFileInfo #endregion + private const string signatureStartString = "# SIG # Begin signature block"; + #region Constructor private PSScriptFileInfo() {} @@ -182,7 +179,7 @@ public PSScriptFileInfo( string[] releaseNotes, string privateData, string description, - string[] endOfFileContents + string endOfFileContents ) { if (String.IsNullOrEmpty(author)) @@ -213,20 +210,19 @@ string[] endOfFileContents #region Internal Static Methods - // one method that takes .ps1 file and creates hashtable /// /// Parses content of .ps1 file into a hashtable /// internal static bool TryParseScript( string scriptFileInfoPath, out Hashtable parsedScriptMetadata, - out string[] endOfFileContents, + out string endOfFileContents, out ErrorRecord[] errors ) { errors = new ErrorRecord[]{}; parsedScriptMetadata = new Hashtable(); - endOfFileContents = new string[]{}; + endOfFileContents = String.Empty; List errorsList = new List(); // Parse the script file @@ -296,10 +292,7 @@ out ErrorRecord[] errors return false; } - // get .REQUIREDMODULES property, by accessing the ScriptBlockAst.ScriptRequirements.RequiredModules - // ex of ScriptRequirements - // ex of RequiredModules - // TODO: (Anam) in comment include ex of ScriptRequirements and RequiredModules (above) + // get .REQUIREDMODULES property, by accessing the System.Management.Automation.Language.ScriptRequirements object ScriptRequirements.RequiredModules property ScriptRequirements parsedScriptRequirements = ast.ScriptRequirements; ReadOnlyCollection parsedModules = new List().AsReadOnly(); @@ -311,9 +304,8 @@ out ErrorRecord[] errors // 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"; + string commentPattern = @"^<#PSScriptInfo"; Regex rg = new Regex(commentPattern); Token psScriptInfoCommentToken = commentTokens.Where(a => rg.IsMatch(a.Extent.Text)).FirstOrDefault(); @@ -328,9 +320,9 @@ out ErrorRecord[] errors return false; } - // TODO: add ^ - // that would get each line that matches <#PSScriptInfo at the start, which is only line 1 /** + should now have a Token with text like this: + <#PSScriptInfo .VERSION 1.0 @@ -365,13 +357,13 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd #> */ - string[] newlineDelimeters = new string[]{"\r\n", "\n"}; // TODO should I also have \n? + 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 + If comment line count is not more than two, it doesn't have the any metadata property and comment block would look like: <#PSScriptInfo @@ -406,14 +398,10 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd } // get end of file contents - // TODO: (Anam) when updating write warning to user that the signature will be invalidated, needs to be regenerated string[] totalFileContents = File.ReadAllLines(scriptFileInfoPath); var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); - if (contentAfterDescription.Count() > 0) - { - endOfFileContents = contentAfterDescription.ToArray(); - } + endOfFileContents = String.Join("\n", contentAfterDescription.ToArray()); return true; } @@ -426,11 +414,10 @@ internal static bool TryValidateScript( out ErrorRecord[] errors ) { - // TODO: retuan all errors at once, wait til end + // TODO: I think hashtable keys should be lower cased // required properties for script file (.ps1 file) are: Author, Version, Guid, Description // Description gets validated in TryParseScript() when getting the property - // TODO: I think hashtable keys should be lower cased List errorsList = new List(); @@ -440,8 +427,6 @@ out ErrorRecord[] errors var ex = new ArgumentException(message); var psScriptMissingVersionError = new ErrorRecord(ex, "psScriptMissingVersion", ErrorCategory.ParserError, null); errorsList.Add(psScriptMissingVersionError); - errors = errorsList.ToArray(); - return false; } if (!parsedScriptMetadata.ContainsKey("AUTHOR") || String.IsNullOrEmpty((string) parsedScriptMetadata["AUTHOR"])) @@ -450,8 +435,6 @@ out ErrorRecord[] errors var ex = new ArgumentException(message); var psScriptMissingAuthorError = new ErrorRecord(ex, "psScriptMissingAuthor", ErrorCategory.ParserError, null); errorsList.Add(psScriptMissingAuthorError); - errors = errorsList.ToArray(); - return false;; } if (!parsedScriptMetadata.ContainsKey("GUID") || String.IsNullOrEmpty((string) parsedScriptMetadata["GUID"])) @@ -460,11 +443,14 @@ out ErrorRecord[] errors var ex = new ArgumentException(message); var psScriptMissingGuidError = new ErrorRecord(ex, "psScriptMissingGuid", ErrorCategory.ParserError, null); errorsList.Add(psScriptMissingGuidError); - errors = errorsList.ToArray(); - return false; } errors = errorsList.ToArray(); + if (errors.Length > 0) + { + return false; + } + return true; } @@ -496,7 +482,7 @@ internal static bool TryParseScriptIntoPSScriptInfo( if (!TryParseScript( scriptFileInfoPath: scriptFileInfoPath, parsedScriptMetadata: out Hashtable parsedScriptMetadata, - endOfFileContents: out string[] endofFileContents, + endOfFileContents: out string endofFileContents, errors: out ErrorRecord[] parseErrors )) { @@ -729,12 +715,6 @@ internal static bool TryUpdateScriptFileContents( return false; } - // TODO: move into TryCreateScriptFileInfoString, if it exists then add, if not you dont - if (scriptInfo.EndOfFileContents.Length > 0) - { - psScriptFileContents += "\n" + String.Join("\n", scriptInfo.EndOfFileContents); - } - updatedPSScriptFileContents = psScriptFileContents; return true; } @@ -747,7 +727,7 @@ internal static bool TryUpdateScriptFileContents( /// 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 /// - public bool TryCreateScriptFileInfoString( + internal bool TryCreateScriptFileInfoString( // string filePath, out string pSScriptFileString, // this is the string with the contents we want to put in the new ps1 file out ErrorRecord[] errors @@ -805,6 +785,21 @@ out ErrorRecord[] errors // 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)) + { + if (EndOfFileContents.Contains(signatureStartString)) + { + RemoveSignatureString(); + } + + pSScriptFileString += "\n" + EndOfFileContents; + } + fileContentsSuccessfullyCreated = true; return fileContentsSuccessfullyCreated; } @@ -851,17 +846,9 @@ Feature 5 #> */ - if (String.IsNullOrEmpty(Author) || Version == null) - { - var exMessage = "PSScriptInfo must contain values for Author and Version. Ensure both of these are present."; - var ex = new ArgumentException(exMessage); - var PSScriptInfoMissingAuthorOrVersionError = new ErrorRecord(ex, "PSScriptInfoMissingAuthorOrVersion", ErrorCategory.InvalidArgument, null); - error = PSScriptInfoMissingAuthorOrVersionError; - return pSScriptInfoSuccessfullyCreated; - } - pSScriptInfoSuccessfullyCreated = true; List psScriptInfoLines = new List(); + psScriptInfoLines.Add("<#PSScriptInfo"); psScriptInfoLines.Add(String.Format(".VERSION {0}", Version.ToString())); psScriptInfoLines.Add(String.Format(".GUID {0}", Guid.ToString())); @@ -904,7 +891,6 @@ out string psRequiresString psRequiresLines.Add("\n"); psRequiresString = String.Join("\n", psRequiresLines); - // TODO: Does the GUID come in for ModuleSpecification(string) constructed object's ToString() output? } } @@ -943,6 +929,9 @@ out ErrorRecord error psHelpInfoSuccessfullyCreated = true; psHelpInfoLines.Add("<#\n"); psHelpInfoLines.Add(String.Format(".DESCRIPTION\n{0}", Description)); + Console.WriteLine("start of description:"); + Console.WriteLine(Description); + Console.WriteLine("End of description"); if (!String.IsNullOrEmpty(Synopsis)) { @@ -995,13 +984,19 @@ out ErrorRecord error } /// - /// Ensure no fields (passed as stringToValidate) contains '<#' or '#>' (would break comment section) + /// Ensure no fields (passed as stringToValidate) contains '<#' or '#>' (would break AST block section) /// private bool StringContainsComment(string stringToValidate) { return stringToValidate.Contains("<#") || stringToValidate.Contains("#>"); } + private void RemoveSignatureString() + { + int signatureStartIndex = EndOfFileContents.IndexOf(signatureStartString); + EndOfFileContents = EndOfFileContents.Substring(0, signatureStartIndex); + } + #endregion } diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 3f1c1f1f3..edcd85035 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -19,14 +19,6 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets [Cmdlet(VerbsData.Update, "PSScriptFileInfo")] public sealed class UpdatePSScriptFileInfo : PSCmdlet { - #region Members - // TODO: make these variables - private Uri _projectUri; - private Uri _licenseUri; - private Uri _iconUri; - - #endregion - #region Parameters /// @@ -98,13 +90,6 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet [ValidateNotNullOrEmpty()] public string LicenseUri { get; set; } - /// - /// If specified, passes the contents of the created .ps1 file to the console - /// If -Path is not specified, then .ps1 contents will just be written out for the user - /// - [Parameter] - public SwitchParameter PassThru { get; set; } - /// /// The path the .ps1 script info file will be created at /// @@ -133,6 +118,13 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet [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 /// @@ -154,12 +146,6 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet [ValidateNotNullOrEmpty()] public string[] Tags { get; set; } - /// - /// If specified, it validates the updated script - /// - [Parameter] - public SwitchParameter Validate { get; set; } - /// /// The version of the script /// @@ -169,30 +155,38 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet #endregion + #region Private Members + + private const string signatureStartString = "# SIG # Begin signature block"; + + #endregion + #region Methods protected override void ProcessRecord() { - // validate Uri related parameters passed in as strings + Uri projectUri = null; if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri, cmdletPassedIn: this, - uriResult: out _projectUri, + 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, + 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, + uriResult: out iconUri, errorRecord: out ErrorRecord iconErrorRecord)) { ThrowTerminatingError(iconErrorRecord); @@ -228,16 +222,17 @@ protected override void ProcessRecord() ModuleSpecification[] validatedRequiredModuleSpecifications = new ModuleSpecification[]{}; if (RequiredModules != null && RequiredModules.Length > 0) { - Utils.CreateModuleSpecification( + if (!Utils.TryCreateModuleSpecification( moduleSpecHashtables: RequiredModules, out validatedRequiredModuleSpecifications, - out ErrorRecord[] moduleSpecErrors); - if (moduleSpecErrors.Length > 0) + out ErrorRecord[] moduleSpecErrors)) { foreach (ErrorRecord err in moduleSpecErrors) { WriteError(err); } + + return; } } @@ -260,6 +255,18 @@ protected override void ProcessRecord() return; } + + if (parsedScriptInfo.EndOfFileContents.Contains(signatureStartString)) + { + WriteWarning("This script contains a signature and cannot be updated without invalidating the script"); + if (!RemoveSignature) + { + var exMessage = "Cannot update script as the .ps1 contains a signature. Either use -RemoveSignature paramter or manaully remove signature block and re-run cmdlet."; + var ex = new PSInvalidOperationException(exMessage); + var ScriptToBeUpdatedContainsSignatureError = new ErrorRecord(ex, "ScriptToBeUpdatedContainsSignature", ErrorCategory.InvalidOperation, null); + ThrowTerminatingError(ScriptToBeUpdatedContainsSignatureError); + } + } if (!PSScriptFileInfo.TryUpdateScriptFileContents( scriptInfo: parsedScriptInfo, @@ -271,9 +278,9 @@ protected override void ProcessRecord() companyName: CompanyName, copyright: Copyright, tags: Tags, - licenseUri: _licenseUri, - projectUri: _projectUri, - iconUri: _iconUri, + licenseUri: licenseUri, + projectUri: projectUri, + iconUri: iconUri, requiredModules: validatedRequiredModuleSpecifications, externalModuleDependencies: ExternalModuleDependencies, requiredScripts: RequiredScripts, @@ -291,8 +298,6 @@ protected override void ProcessRecord() return; } - // TODO: put this in a try catch - // write string of file contents to a temp file string tempScriptFilePath = null; try { @@ -303,7 +308,11 @@ protected override void ProcessRecord() } catch(Exception e) { - // TODO + WriteError(new ErrorRecord( + new PSInvalidOperationException($"Could not update .ps1 file due to: {e.Message}"), + "FileIOErrorDuringUpdate", + ErrorCategory.InvalidArgument, + this)); } finally { @@ -311,13 +320,7 @@ protected override void ProcessRecord() { File.Delete(tempScriptFilePath); } - } - - // TODO: ceck with Sydney if we wnat to return PSScriptFileInfo obj? - if (PassThru) - { - WriteObject(updatedPSScriptFileContents); - } + } } #endregion diff --git a/src/code/Utils.cs b/src/code/Utils.cs index ae8644ffc..f85f72daf 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -831,11 +831,12 @@ public static Hashtable ConvertJsonToHashtable( return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; } - public static void CreateModuleSpecification( + 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(); @@ -845,10 +846,11 @@ public static void CreateModuleSpecification( // 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 is missing a key 'ModuleName' and associated value, which is required for each module specification entry"; + 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; } @@ -857,6 +859,7 @@ public static void CreateModuleSpecification( ModuleSpecification currentModuleSpec; if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) { + // TODO: try with malformed data, does it throw? // pass to ModuleSpecification(string) constructor currentModuleSpec = new ModuleSpecification(moduleSpecName); if (currentModuleSpec != null) @@ -865,10 +868,12 @@ public static void CreateModuleSpecification( } else { - var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + 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 @@ -877,15 +882,15 @@ public static void CreateModuleSpecification( 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; // TODO: ANAM this can be the default + Guid moduleSpecGuid = moduleSpec.ContainsKey("Guid") ? (Guid) moduleSpec["Guid"] : Guid.Empty; if (String.IsNullOrEmpty(moduleSpecMaxVersion) && String.IsNullOrEmpty(moduleSpecModuleVersion) && String.IsNullOrEmpty(moduleSpecRequiredVersion)) { - var exMessage = String.Format("ModuleSpecification hashtable requires one of the following keys: MaximumVersion, ModuleVersion, RequiredVersion and failed to be created for {0}", moduleSpecName); + 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; } @@ -919,7 +924,7 @@ public static void CreateModuleSpecification( } else { - var exMessage = String.Format("ModuleSpecification object was not able to be created for {0}", moduleSpecName); + 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); @@ -929,6 +934,7 @@ public static void CreateModuleSpecification( errors = errorList.ToArray(); validatedModuleSpecs = moduleSpecsList.ToArray(); + return moduleSpecCreatedSuccessfully; } #endregion diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index 80fa4808d..84369e1ab 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -29,8 +29,7 @@ Describe "Test New-PSScriptFileInfo" { It "create .ps1 file with minimal required fields" { $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testScript.ps1" $scriptDescription = "this is a test script" - $res = New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -PassThru - $res | Should -Not -BeNullOrEmpty + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue } @@ -39,8 +38,7 @@ Describe "Test New-PSScriptFileInfo" { $relativeCurrentPath = Get-Location $scriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "testScript.ps1" $scriptDescription = "this is a test script" - $res = New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -PassThru - $res | Should -Not -BeNullOrEmpty + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue Remove-Item -Path (Join-Path -Path $relativeCurrentPath -ChildPath "testScript.ps1") @@ -56,8 +54,7 @@ Describe "Test New-PSScriptFileInfo" { $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testScript2.ps1" $scriptDescription = "this is a test script" - $res = New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -RequiredModules $requiredModulesHashtables -PassThru - $res | Should -Not -BeNullOrEmpty + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -RequiredModules $requiredModulesHashtables Test-PSScriptFileInfo -FilePath $scriptFilePath | Should -BeTrue } diff --git a/test/UpdatePSScriptFileInfo.Tests.ps1 b/test/UpdatePSScriptFileInfo.Tests.ps1 index 4138c616d..d626ab369 100644 --- a/test/UpdatePSScriptFileInfo.Tests.ps1 +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -21,7 +21,7 @@ Describe "Test Update-PSScriptFileInfo" { BeforeEach { $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testscript.ps1" $scriptDescription = "this is a test script" - New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription -PassThru + New-PSScriptFileInfo -FilePath $scriptFilePath -Description $scriptDescription } AfterEach { @@ -51,7 +51,7 @@ Describe "Test Update-PSScriptFileInfo" { } It "update script file Version property with prerelease version" { - Update-PSScriptFileInfo -FilePath $scriptFilePath -Version "3.0.0-alpha" + Update-PSScriptFileInfo -FilePath $scriptFilePath -Version "3.0.0-alpha" -verbose Test-PSScriptFileInfo $scriptFilePath | Should -Be $true } @@ -142,6 +142,4 @@ Describe "Test Update-PSScriptFileInfo" { Update-PSScriptFileInfo -FilePath $scriptFilePath -Tags $testTags Test-PSScriptFileInfo $scriptFilePath | Should -Be $true } - - # Validate param needs to be tested } From 8fc0b9fa64c5f1f697ed549136b598e481fe1465 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 22 Jun 2022 13:21:39 -0400 Subject: [PATCH 63/86] fix Description block newline padding bug --- src/code/PSScriptFileInfo.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 979add1bd..d12f0e262 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -789,7 +789,6 @@ out ErrorRecord[] errors // 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)) { if (EndOfFileContents.Contains(signatureStartString)) @@ -896,7 +895,7 @@ out string psRequiresString /// /// Used when creating .ps1 file's contents. - /// This creates the help comment string: <# \n .DESCRIPTION #> + /// This creates the help comment string: <# \n .DESCRIPTION{mydescription}\n\n#> /// private bool GetScriptCommentHelpInfo( out string psHelpInfo, @@ -929,9 +928,6 @@ out ErrorRecord error psHelpInfoSuccessfullyCreated = true; psHelpInfoLines.Add("<#\n"); psHelpInfoLines.Add(String.Format(".DESCRIPTION\n{0}", Description)); - Console.WriteLine("start of description:"); - Console.WriteLine(Description); - Console.WriteLine("End of description"); if (!String.IsNullOrEmpty(Synopsis)) { @@ -978,8 +974,9 @@ out ErrorRecord error psHelpInfoLines.Add(String.Format(".FUNCTIONALITY\n{0}", String.Join("\n", Functionality))); } - psHelpInfoLines.Add("#>"); psHelpInfo = String.Join("\n", psHelpInfoLines); + psHelpInfo = psHelpInfo.TrimEnd('\n'); + psHelpInfo += "\n\n#>"; return psHelpInfoSuccessfullyCreated; } From d7d763bdd3645cb5a393b5eaa955d5ab9cd09313 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 22 Jun 2022 15:36:27 -0400 Subject: [PATCH 64/86] add tests for RemoveSignature parameter --- src/code/UpdatePSScriptFileInfo.cs | 2 +- test/UpdatePSScriptFileInfo.Tests.ps1 | 26 +++++++ .../testScripts/ScriptWithSignature.ps1 | 75 +++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 test/testFiles/testScripts/ScriptWithSignature.ps1 diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index edcd85035..17dd3dd10 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -258,7 +258,7 @@ protected override void ProcessRecord() if (parsedScriptInfo.EndOfFileContents.Contains(signatureStartString)) { - WriteWarning("This script contains a signature and cannot be updated without invalidating the script"); + WriteWarning("This script contains a signature and cannot be updated without invalidating the current script signature"); if (!RemoveSignature) { var exMessage = "Cannot update script as the .ps1 contains a signature. Either use -RemoveSignature paramter or manaully remove signature block and re-run cmdlet."; diff --git a/test/UpdatePSScriptFileInfo.Tests.ps1 b/test/UpdatePSScriptFileInfo.Tests.ps1 index d626ab369..c987e407e 100644 --- a/test/UpdatePSScriptFileInfo.Tests.ps1 +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -142,4 +142,30 @@ Describe "Test Update-PSScriptFileInfo" { Update-PSScriptFileInfo -FilePath $scriptFilePath -Tags $testTags Test-PSScriptFileInfo $scriptFilePath | Should -Be $true } + + It "update signed script when using 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 "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" -RemoveSignature + Test-PSScriptFileInfo -FilePath $tmpScriptFilePath | Should -Be $true + } } 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 From f46501557293c2f86c9bd6089c53f80bc318fb73 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 22 Jun 2022 16:07:25 -0400 Subject: [PATCH 65/86] add try catch around ModuleSpecification constructor call --- src/code/Utils.cs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index f85f72daf..e677649d0 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -856,12 +856,13 @@ public static bool TryCreateModuleSpecification( // at this point it must contain ModuleName key. string moduleSpecName = (string) moduleSpec["ModuleName"]; - ModuleSpecification currentModuleSpec; - if (moduleSpec.Keys.Count == 1 || (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion") && !moduleSpec.ContainsKey("Guid"))) + ModuleSpecification currentModuleSpec = null; + if (!moduleSpec.ContainsKey("MaximumVersion") && !moduleSpec.ContainsKey("ModuleVersion") && !moduleSpec.ContainsKey("RequiredVersion")) { - // TODO: try with malformed data, does it throw? // 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); @@ -917,17 +918,21 @@ public static bool TryCreateModuleSpecification( moduleSpecHash.Add("RequiredVersion", moduleSpecRequiredVersion); } - currentModuleSpec = new ModuleSpecification(moduleSpecHash); - if (currentModuleSpec != null) + try { - moduleSpecsList.Add(currentModuleSpec); + currentModuleSpec = new ModuleSpecification(moduleSpecHash); } - else + catch (Exception e) { - var exMessage = $"ModuleSpecification object was not able to be created for {moduleSpecName}"; - var ex = new ArgumentException(exMessage); + 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); } } } From a50251c83a2d4638a2b6aa09ed9d37fcb8d3e0ae Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 22 Jun 2022 16:34:16 -0400 Subject: [PATCH 66/86] clean up code comments --- src/code/PSScriptFileInfo.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index d12f0e262..70a288e45 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -141,17 +141,17 @@ public sealed class PSScriptFileInfo public string[] Links { get; set; } = new string[]{}; /// - /// TODO: what is this? + /// The components for the script /// public string[] Component { get; set; } = new string[]{}; /// - /// TODO: what is this? + /// The roles for the script /// public string[] Role { get; set; } = new string[]{}; /// - /// TODO: what is this? + /// The functionality for the /// public string[] Functionality { get; set; } = new string[]{}; @@ -414,8 +414,6 @@ internal static bool TryValidateScript( out ErrorRecord[] errors ) { - // TODO: I think hashtable keys should be lower cased - // required properties for script file (.ps1 file) are: Author, Version, Guid, Description // Description gets validated in TryParseScript() when getting the property @@ -517,7 +515,7 @@ internal static bool TryParseScriptIntoPSScriptInfo( string parsedCompanyName = (string) parsedScriptMetadata["COMPANYNAME"] ?? String.Empty; string parsedCopyright = (string) parsedScriptMetadata["COPYRIGHT"] ?? String.Empty; - string parsedPrivateData = (string) parsedScriptMetadata["PRIVATEDATA"] ?? String.Empty; // TODO: fix PrivateData bug? or already fixed? + string parsedPrivateData = (string) parsedScriptMetadata["PRIVATEDATA"] ?? String.Empty; string[] parsedTags = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["TAGS"]); string[] parsedExternalModuleDependencies = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["EXTERNALMODULEDEPENDENCIES"]); From 3a6e0a80122edc6272ace907487ad4f2e995c412 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 22 Jun 2022 16:41:09 -0400 Subject: [PATCH 67/86] use private setters --- src/code/PSScriptFileInfo.cs | 56 +++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 70a288e45..8c275e892 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -27,138 +27,142 @@ public sealed class PSScriptFileInfo /// /// the Version of the script /// - public NuGetVersion Version { get; set; } + public NuGetVersion Version { get; private set; } /// /// the GUID for the script /// - public Guid Guid { get; set; } + public Guid Guid { get; private set; } /// /// the author for the script /// - public string Author { get; set; } + public string Author { get; private set; } /// /// the name of the company owning the script /// - public string CompanyName { get; set; } + public string CompanyName { get; private set; } /// /// the copyright information for the script /// - public string Copyright { get; set; } + public string Copyright { get; private set; } /// /// the tags for the script /// - public string[] Tags { get; set; } + public string[] Tags { get; private set; } /// /// the Uri for the license of the script /// - public Uri LicenseUri { get; set; } + public Uri LicenseUri { get; private set; } /// /// the Uri for the project relating to the script /// - public Uri ProjectUri { get; set; } + public Uri ProjectUri { get; private set; } /// /// the Uri for the icon relating to the script /// - public Uri IconUri { get; set; } + 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; set; } = new ModuleSpecification[]{}; + public ModuleSpecification[] RequiredModules { get; private set; } = new ModuleSpecification[]{}; /// /// the list of external module dependencies for the script /// - public string[] ExternalModuleDependencies { get; set; } = new string[]{}; + public string[] ExternalModuleDependencies { get; private set; } = new string[]{}; /// /// the list of required scripts for the parent script /// - public string[] RequiredScripts { get; set; } = new string[]{}; + public string[] RequiredScripts { get; private set; } = new string[]{}; /// /// the list of external script dependencies for the script /// - public string[] ExternalScriptDependencies { get; set; } = new string[]{}; + public string[] ExternalScriptDependencies { get; private set; } = new string[]{}; /// /// the release notes relating to the script /// - public string[] ReleaseNotes { get; set; } = new string[]{}; + public string[] ReleaseNotes { get; private set; } = new string[]{}; /// /// The private data associated with the script /// - public string PrivateData { get; set; } + public string PrivateData { get; private set; } /// /// The description of the script /// - public string Description { get; set; } + public string Description { get; private set; } /// /// End of file contents for the .ps1 file /// - public string EndOfFileContents { get; set; } = String.Empty; + public string EndOfFileContents { get; private set; } = String.Empty; /// /// The synopsis of the script /// - public string Synopsis { get; set; } + public string Synopsis { get; private set; } /// /// The example(s) relating to the script's usage /// - public string[] Example { get; set; } = new string[]{}; + public string[] Example { get; private set; } = new string[]{}; /// /// The inputs to the script /// - public string[] Inputs { get; set; } = new string[]{}; + public string[] Inputs { get; private set; } = new string[]{}; /// /// The outputs to the script /// - public string[] Outputs { get; set; } = new string[]{}; + public string[] Outputs { get; private set; } = new string[]{}; /// /// The notes for the script /// - public string[] Notes { get; set; } = new string[]{}; + public string[] Notes { get; private set; } = new string[]{}; /// /// The links for the script /// - public string[] Links { get; set; } = new string[]{}; + public string[] Links { get; private set; } = new string[]{}; /// /// The components for the script /// - public string[] Component { get; set; } = new string[]{}; + public string[] Component { get; private set; } = new string[]{}; /// /// The roles for the script /// - public string[] Role { get; set; } = new string[]{}; + public string[] Role { get; private set; } = new string[]{}; /// /// The functionality for the /// - public string[] Functionality { get; set; } = new string[]{}; + 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() {} From 0632927394424e8e0294bc950ed314ca8cd06ab9 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 22 Jun 2022 16:51:57 -0400 Subject: [PATCH 68/86] incorporating CR feedback --- src/code/NewPSScriptFileInfo.cs | 2 +- src/code/PSScriptFileInfo.cs | 2 +- src/code/Utils.cs | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index b70e9c938..9e26402f3 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -250,4 +250,4 @@ protected override void ProcessRecord() #endregion } -} \ No newline at end of file +} diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 8c275e892..bf2163cdc 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -999,4 +999,4 @@ private void RemoveSignatureString() #endregion } -} \ No newline at end of file +} diff --git a/src/code/Utils.cs b/src/code/Utils.cs index e677649d0..b68c4e9c5 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -721,8 +721,6 @@ public static List GetAllInstallationPaths( private readonly static Version PSVersion6 = new Version(6, 0); private static void GetStandardPlatformPaths( - - PSCmdlet psCmdlet, out string myDocumentsPath, out string programFilesPath) From b758818fe3e796e26a538306b75e0065ed8ca301 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 22 Jun 2022 16:58:59 -0400 Subject: [PATCH 69/86] fix typo --- src/PowerShellGet.psd1 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index 60b5ff2c9..b98d29797 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -24,7 +24,6 @@ 'New-PSScriptFileInfo', 'Publish-PSResource', 'Test-PSScriptFileInfo', - 'Test-VersionEquality', 'Uninstall-PSResource', 'Unregister-PSResourceRepository', 'Update-PSResource', From edd069995ad3642c06a5ae0d2da8288d353c6b44 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 22 Jun 2022 17:16:46 -0400 Subject: [PATCH 70/86] make hastable keys insensitive case --- help/New-PSScriptFileInfo.md | 17 +---------------- help/Update-PSScriptFileInfo.md | 2 +- src/code/PSScriptFileInfo.cs | 2 +- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/help/New-PSScriptFileInfo.md b/help/New-PSScriptFileInfo.md index 25682209f..963435604 100644 --- a/help/New-PSScriptFileInfo.md +++ b/help/New-PSScriptFileInfo.md @@ -13,7 +13,7 @@ Creates a new .ps1 file containing metadata for the script, which is used when p ## SYNTAX ``` -New-PSScriptFileInfo [-FilePath ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-PassThru] [-Force] [-WhatIf] [] +New-PSScriptFileInfo [-FilePath ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-Force] [-WhatIf] [] ``` ## DESCRIPTION @@ -279,21 +279,6 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -PassThru -When specified, displays the PSScriptFileInfo object representing the script metadata. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - ### -Force When specified, if the file at the specified -FilePath exists it overwrites that file. diff --git a/help/Update-PSScriptFileInfo.md b/help/Update-PSScriptFileInfo.md index 0ed54b071..3a357f34d 100644 --- a/help/Update-PSScriptFileInfo.md +++ b/help/Update-PSScriptFileInfo.md @@ -13,7 +13,7 @@ Updates an existing .ps1 file with requested properties and ensures it's valid. ## SYNTAX ``` -Update-PSScriptFileInfo [-FilePath ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-WhatIf] [-Validate] [] [] +Update-PSScriptFileInfo [-FilePath ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-WhatIf] [] [] ``` ## DESCRIPTION diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index bf2163cdc..a99e14d0d 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -225,7 +225,7 @@ out ErrorRecord[] errors ) { errors = new ErrorRecord[]{}; - parsedScriptMetadata = new Hashtable(); + parsedScriptMetadata = new Hashtable(StringComparer.InvariantCultureIgnoreCase); endOfFileContents = String.Empty; List errorsList = new List(); From fed52325c59a36aff652e19dad1db4397f208d44 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 22 Jun 2022 17:29:41 -0400 Subject: [PATCH 71/86] remove help docs for now and include in a separate PR --- help/New-PSScriptFileInfo.md | 335 -------------------------------- help/Test-PSScriptFileInfo.md | 80 -------- help/Update-PSScriptFileInfo.md | 319 ------------------------------ 3 files changed, 734 deletions(-) delete mode 100644 help/New-PSScriptFileInfo.md delete mode 100644 help/Test-PSScriptFileInfo.md delete mode 100644 help/Update-PSScriptFileInfo.md diff --git a/help/New-PSScriptFileInfo.md b/help/New-PSScriptFileInfo.md deleted file mode 100644 index 963435604..000000000 --- a/help/New-PSScriptFileInfo.md +++ /dev/null @@ -1,335 +0,0 @@ ---- -external help file: PowerShellGet.dll-Help.xml -Module Name: PowerShellGet -online version: -schema: 2.0.0 ---- - -# New-ScriptFileInfo - -## SYNOPSIS -Creates a new .ps1 file containing metadata for the script, which is used when publishing a script package. - -## SYNTAX - -``` -New-PSScriptFileInfo [-FilePath ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-Force] [-WhatIf] [] -``` - -## DESCRIPTION -The New-PSScriptFileInfo cmdlet creates a .ps1 file containing metadata for the script. - -## EXAMPLES - - -## PARAMETERS - -### -FilePath -The path the .ps1 script info file will be created at. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Version -The version of the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Author -The author of the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Description -The description of the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -GUID -The GUID for the script. - -```yaml -Type: System.Guid -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -CompanyName -The name of the company owning the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Copyright -The copyright information for the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -RequiredModules -The list of modules required for the script. - -```yaml -Type: Microsoft.PowerShell.Commands.ModuleSpecification[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ExternalModuleDependencies -The list of external module dependencies taken by this script - -```yaml -Type: System.String[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -RequiredScripts -The list of scripts required by the script - -```yaml -Type: System.String[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ExternalScriptDependencies -The list of external script dependencies taken by this script - -```yaml -Type: System.String[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Tags -The tags associated with the script. - -```yaml -Type: System.String[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProjectUri -The Uri for the project associated with the script - -```yaml -Type: System.Uri -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LicenseUri -The Uri for the license associated with the script - -```yaml -Type: System.Uri -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -IconUri -The Uri for the icon associated with the script - -```yaml -Type: System.Uri -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ReleaseNotes -The release notes for the script - -```yaml -Type: System.String[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -PrivateData -The private data associated with the script - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Force -When specified, if the file at the specified -FilePath exists it overwrites that file. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). - -## OUTPUTS - -### Microsoft.PowerShell.PowerShellGet.UtilClasses.PSResourceInfo -``` -PSScriptFileInfo : { - Version - Guid - Author - CompanyName - Copyright - Tags - LicenseUri - ProjectUri - IconUri - RequiredModules - ExternalModuleDependencies - RequiredScripts - ExternalScriptDependencies - ReleaseNotes - PrivateData - Description - Synopsis - Example - Inputs - Outputs - Notes - Links - Component - Role - Functionality -} -``` - -## NOTES - -## RELATED LINKS diff --git a/help/Test-PSScriptFileInfo.md b/help/Test-PSScriptFileInfo.md deleted file mode 100644 index 543d3615a..000000000 --- a/help/Test-PSScriptFileInfo.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -external help file: PowerShellGet.dll-Help.xml -Module Name: PowerShellGet -online version: -schema: 2.0.0 ---- - -# New-ScriptFileInfo - -## SYNOPSIS -Tests a .ps1 file at the specified path to ensure it is valid. - -## SYNTAX - -``` -Test-PSScriptFileInfo [-FilePath ] [] -``` - -## DESCRIPTION -The Test-PSScriptFileInfo cmdlet tests a .ps1 file at the specified path to ensure it is valid. - -## EXAMPLES - - -## PARAMETERS - -### -FilePath -The path the .ps1 script info file will be created at. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). - -## OUTPUTS - -### Microsoft.PowerShell.PowerShellGet.UtilClasses.PSResourceInfo -``` -PSScriptFileInfo : { - Version - Guid - Author - CompanyName - Copyright - Tags - LicenseUri - ProjectUri - IconUri - RequiredModules - ExternalModuleDependencies - RequiredScripts - ExternalScriptDependencies - ReleaseNotes - PrivateData - Description - Synopsis - Example - Inputs - Outputs - Notes - Links - Component - Role - Functionality -} -``` - -## NOTES - -## RELATED LINKS diff --git a/help/Update-PSScriptFileInfo.md b/help/Update-PSScriptFileInfo.md deleted file mode 100644 index 3a357f34d..000000000 --- a/help/Update-PSScriptFileInfo.md +++ /dev/null @@ -1,319 +0,0 @@ ---- -external help file: PowerShellGet.dll-Help.xml -Module Name: PowerShellGet -online version: -schema: 2.0.0 ---- - -# New-ScriptFileInfo - -## SYNOPSIS -Updates an existing .ps1 file with requested properties and ensures it's valid. - -## SYNTAX - -``` -Update-PSScriptFileInfo [-FilePath ] [-Version ] [-Author ] [-Description ] [-Guid ] [-CompanyName ] [-Copyright >] [-RequiredModules ] [-ExternalModuleDependencies ] [-RequiredScripts ] [-ExternalScriptDependencies ] [-Tags ] [-ProjectUri ] [-LicenseUri ] [-IconUri ] [-ReleaseNotes ] [-PrivateData ] [-WhatIf] [] [] -``` - -## DESCRIPTION -Updates an existing .ps1 script file with the requested properties and ensures it's valid. - -## EXAMPLES - -## PARAMETERS - -### -FilePath -The path the .ps1 script info file will be created at. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Version -The version of the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Author -The author of the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Description -The description of the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -GUID -The GUID for the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -CompanyName -The name of the company owning the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Copyright -The copyright information for the script. - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -RequiredModules -The list of modules required for the script. - -```yaml -Type: Microsoft.PowerShell.Commands.ModuleSpecification[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ExternalModuleDependencies -The list of external module dependencies taken by this script - -```yaml -Type: System.String[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -RequiredScripts -The list of scripts required by the script - -```yaml -Type: System.String[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ExternalScriptDependencies -The list of external script dependencies taken by this script - -```yaml -Type: System.String[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Tags -The tags associated with the script. - -```yaml -Type: System.String[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProjectUri -The Uri for the project associated with the script - -```yaml -Type: System.Uri -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LicenseUri -The Uri for the license associated with the script - -```yaml -Type: System.Uri -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -IconUri -The Uri for the icon associated with the script - -```yaml -Type: System.Uri -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ReleaseNotes -The release notes for the script - -```yaml -Type: System.String[] -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -PrivateData -The private data associated with the script - -```yaml -Type: System.String -Parameter Sets: -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). - -## OUTPUTS - -### Microsoft.PowerShell.PowerShellGet.UtilClasses.PSResourceInfo -``` -PSScriptFileInfo : { - Version - Guid - Author - CompanyName - Copyright - Tags - LicenseUri - ProjectUri - IconUri - RequiredModules - ExternalModuleDependencies - RequiredScripts - ExternalScriptDependencies - ReleaseNotes - PrivateData - Description - Synopsis - Example - Inputs - Outputs - Notes - Links - Component - Role - Functionality -} -``` - -## NOTES - -## RELATED LINKS From 6d2d3c3f2690fabce79aa1eca15de83c7fab5dc6 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 5 Jul 2022 17:14:07 -0400 Subject: [PATCH 72/86] New-PSScriptFileInfo- add code to resolve non-existant (yet) relative paths --- src/code/NewPSScriptFileInfo.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 9e26402f3..e0162412e 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -187,8 +187,17 @@ protected override void ProcessRecord() 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(FilePath) && !Force) + 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."; @@ -245,7 +254,7 @@ protected override void ProcessRecord() return; } - File.WriteAllText(FilePath, psScriptFileContents); + File.WriteAllText(resolvedFilePath, psScriptFileContents); } #endregion From 2bdc8d82b0cfd7ab0fe2a10491b547560491bab8 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 6 Jul 2022 10:45:24 -0400 Subject: [PATCH 73/86] remove Force parameter from Update --- src/code/UpdatePSScriptFileInfo.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 17dd3dd10..7671df102 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -63,12 +63,6 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet [ValidateNotNullOrEmpty()] public string[] ExternalScriptDependencies { 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; } - /// /// The GUID for the script /// @@ -213,7 +207,7 @@ protected override void ProcessRecord() if (!File.Exists(resolvedFilePath)) { - var exMessage = "A file does not exist at the location specified"; + 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); From 32f0794cbfbbcd48d97930ae9dc51e03d7ad96b8 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 6 Jul 2022 14:23:42 -0400 Subject: [PATCH 74/86] also populate Copyright, RepositoryLocation and ReleaseNotes properties during Install --- src/code/InstallHelper.cs | 9 ++++++++- src/code/PSResourceInfo.cs | 7 +++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 1854760bb..0ec183f47 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -537,6 +537,9 @@ private List InstallPackage( moduleManifestVersion = parsedMetadataHashtable["ModuleVersion"] as string; pkg.CompanyName = parsedMetadataHashtable["CompanyName"] as string; + pkg.Copyright = parsedMetadataHashtable["Copyright"] as string; + pkg.ReleaseNotes = parsedMetadataHashtable["ReleaseNotes"] as string; + pkg.RepositorySourceLocation = repoUri; // Accept License verification if (!_savePkg && !CallAcceptLicense(pkg, moduleManifest, tempInstallPath, newVersion)) @@ -550,7 +553,11 @@ private List InstallPackage( continue; } } - + else + { + // is script + + } // Delete the extra nupkg related files that are not needed and not part of the module/script DeleteExtraneousFiles(pkgIdentity, tempDirNameVersion); diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 800a93403..839cdc635 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -235,7 +235,7 @@ public sealed class PSResourceInfo public Dictionary AdditionalMetadata { get; } public string Author { get; } public string CompanyName { get; internal set; } - public string Copyright { get; } + public string Copyright { get; internal set; } public Dependency[] Dependencies { get; } public string Description { get; } public Uri IconUri { get; } @@ -250,9 +250,9 @@ public sealed class PSResourceInfo public string Prerelease { get; } public Uri ProjectUri { get; } public DateTime? PublishedDate { get; } - public string ReleaseNotes { get; } + public string ReleaseNotes { get; internal set; } public string Repository { get; } - public string RepositorySourceLocation { get; } + public string RepositorySourceLocation { get; internal set; } public string[] Tags { get; } public ResourceType Type { get; } public DateTime? UpdatedDate { get; } @@ -543,7 +543,6 @@ public static bool TryConvert( repository: repositoryName, repositorySourceLocation: null, tags: ParseMetadataTags(metadataToParse), - // type: ParseMetadataType(metadataToParse, repositoryName, type), type: typeInfo, updatedDate: null, version: ParseMetadataVersion(metadataToParse)); From 2aaa5c1b04d7a634010a7429c0f0020701474468 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 6 Jul 2022 14:30:45 -0400 Subject: [PATCH 75/86] remoe unnecessary directives, remove todo comments from tests --- src/code/NewPSScriptFileInfo.cs | 5 +---- src/code/TestPSScriptFileInfo.cs | 9 +-------- src/code/UpdatePSScriptFileInfo.cs | 5 +---- test/TestPSScriptFileInfo.Tests.ps1 | 5 ----- 4 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index e0162412e..ad6e17252 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -1,15 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.PowerShell.PowerShellGet.UtilClasses; - using System; using System.Collections; -using System.Collections.Generic; using System.IO; using System.Management.Automation; -using System.Text; using Microsoft.PowerShell.Commands; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 25c277003..ea363561d 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -1,17 +1,10 @@ -using System.Net; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.PowerShell.PowerShellGet.UtilClasses; - using System; -using System.Collections; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Management.Automation; -using Microsoft.PowerShell.Commands; -using System.Collections.ObjectModel; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 7671df102..7224de0ed 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -1,15 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.PowerShell.PowerShellGet.UtilClasses; - using System; using System.Collections; -using System.Collections.Generic; using System.IO; using System.Management.Automation; -using System.Text; using Microsoft.PowerShell.Commands; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { diff --git a/test/TestPSScriptFileInfo.Tests.ps1 b/test/TestPSScriptFileInfo.Tests.ps1 index 356dd184c..454a8aa14 100644 --- a/test/TestPSScriptFileInfo.Tests.ps1 +++ b/test/TestPSScriptFileInfo.Tests.ps1 @@ -38,7 +38,6 @@ Describe "Test Test-PSScriptFileInfo" { $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName Test-PSScriptFileInfo $scriptFilePath | Should -Be $false - # TODO: how to test for warnings? (psScriptMissingAuthor) } It "not determine script file with Description field missing as valid" { @@ -46,7 +45,6 @@ Describe "Test Test-PSScriptFileInfo" { $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName Test-PSScriptFileInfo $scriptFilePath | Should -Be $false - # TODO: how to test for warnings? (psScriptMissingDescription) } It "not determine script that is missing Description block altogether as valid" { @@ -54,7 +52,6 @@ Describe "Test Test-PSScriptFileInfo" { $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName Test-PSScriptFileInfo $scriptFilePath | Should -Be $false - # TODO: how to test for warnings? (PSScriptMissingHelpContentCommentBlock) } It "not determine script file Guid as valid" { @@ -62,7 +59,6 @@ Describe "Test Test-PSScriptFileInfo" { $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName Test-PSScriptFileInfo $scriptFilePath | Should -Be $false - # TODO: how to test for warnings? (psScriptMissingGuid) } It "not determine script file missing Version as valid" { @@ -70,6 +66,5 @@ Describe "Test Test-PSScriptFileInfo" { $scriptFilePath = Join-Path $script:testScriptsFolderPath -ChildPath $scriptName Test-PSScriptFileInfo $scriptFilePath | Should -Be $false - # TODO: how to test for warnings? (psScriptMissingVersion) } } From de6cf0e034d05f1f1dcdd5b209ccfe16de54a48d Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 7 Jul 2022 17:13:00 -0400 Subject: [PATCH 76/86] pull in changes from script file info branch including bug fix for releaseNotes,PrivateData --- src/code/InstallHelper.cs | 19 ++++++++++ src/code/NewPSScriptFileInfo.cs | 2 +- src/code/PSScriptFileInfo.cs | 61 +++++++++++++++++++++++------- src/code/UpdatePSScriptFileInfo.cs | 2 +- 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 0ec183f47..de2e21ff9 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -556,6 +556,25 @@ private List InstallPackage( else { // is script + if (!PSScriptFileInfo.TryParseScript( + scriptFileInfoPath: scriptPath, + out Hashtable parsedScriptMetadataHashtable, + out string _, + out ErrorRecord[] errors)) + { + foreach (ErrorRecord err in errors) + { + WriteError(err); + } + + continue; + } + + pkg.CompanyName = parsedScriptMetadataHashtable["COMPANYNAME"] as string; + pkg.Copyright = parsedScriptMetadataHashtable["COPYRIGHT"] as string; + pkg.ReleaseNotes = parsedScriptMetadataHashtable["RELEASENOTES"] as string; + Console.WriteLine((parsedScriptMetadataHashtable["RELEASENOTES"] as string)); + pkg.RepositorySourceLocation = repoUri; } // Delete the extra nupkg related files that are not needed and not part of the module/script diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index ad6e17252..bf0e2f83a 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -128,7 +128,7 @@ public sealed class NewPSScriptFileInfo : PSCmdlet /// [Parameter] [ValidateNotNullOrEmpty()] - public string[] ReleaseNotes { get; set; } + public string ReleaseNotes { get; set; } /// /// The private data associated with the script diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index a99e14d0d..994b2a7b9 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -93,7 +93,7 @@ public sealed class PSScriptFileInfo /// /// the release notes relating to the script /// - public string[] ReleaseNotes { get; private set; } = new string[]{}; + public string ReleaseNotes { get; private set; } = String.Empty; /// /// The private data associated with the script @@ -180,7 +180,7 @@ public PSScriptFileInfo( string[] externalModuleDependencies, string[] requiredScripts, string[] externalScriptDependencies, - string[] releaseNotes, + string releaseNotes, string privateData, string description, string endOfFileContents @@ -204,7 +204,7 @@ string endOfFileContents ExternalModuleDependencies = externalModuleDependencies ?? Utils.EmptyStrArray; RequiredScripts = requiredScripts ?? Utils.EmptyStrArray; ExternalScriptDependencies = externalScriptDependencies ?? Utils.EmptyStrArray; - ReleaseNotes = releaseNotes ?? Utils.EmptyStrArray; + ReleaseNotes = releaseNotes; PrivateData = privateData; Description = description; EndOfFileContents = endOfFileContents; @@ -386,18 +386,48 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd return false; } + keyName = ""; + value = ""; + for (int i = 1; i < commentLines.Count(); i++) { string line = commentLines[i]; - // A line is starting with . conveys a new metadata property + // scenario where line is: .KEY VALUE + // this line contains a new metadata property if (line.Trim().StartsWith(".")) { - // .KEY VALUE + // 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; - parsedScriptMetadata.Add(keyName, value); + } + else if (!(line.Trim()).Equals("#>")) + { + // 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); + } } } @@ -521,11 +551,14 @@ internal static bool TryParseScriptIntoPSScriptInfo( string parsedCopyright = (string) parsedScriptMetadata["COPYRIGHT"] ?? String.Empty; string parsedPrivateData = (string) parsedScriptMetadata["PRIVATEDATA"] ?? String.Empty; + // string parsedReleaseNotes = (string) parsedScriptMetadata["RELEASENOTES"] ?? String.Empty; + // string trimmedReleaseNotes = parsedReleaseNotes.TrimStart('\n'); + 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"]); - string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["RELEASENOTES"]); ReadOnlyCollection parsedModules = (ReadOnlyCollection) parsedScriptMetadata["REQUIREDMODULES"] ?? new ReadOnlyCollection(new List()); @@ -613,7 +646,7 @@ internal static bool TryUpdateScriptFileContents( string[] externalModuleDependencies, string[] requiredScripts, string[] externalScriptDependencies, - string[] releaseNotes, + string releaseNotes, string privateData, string description) { @@ -692,7 +725,7 @@ internal static bool TryUpdateScriptFileContents( scriptInfo.ExternalScriptDependencies = externalScriptDependencies; } - if (releaseNotes != null && releaseNotes.Length != 0) + if (!String.IsNullOrEmpty(releaseNotes)) { scriptInfo.ReleaseNotes = releaseNotes; } @@ -818,8 +851,9 @@ out ErrorRecord error ) { error = null; - bool pSScriptInfoSuccessfullyCreated = false; - pSScriptInfoString = String.Empty; + // TODO: Anam does this really need to return bool? + // bool pSScriptInfoSuccessfullyCreated = false; + // pSScriptInfoString = String.Empty; /** PSScriptInfo comment will be in following format: @@ -847,7 +881,6 @@ Feature 5 #> */ - pSScriptInfoSuccessfullyCreated = true; List psScriptInfoLines = new List(); psScriptInfoLines.Add("<#PSScriptInfo"); @@ -863,12 +896,12 @@ Feature 5 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}", String.Join("\n", ReleaseNotes))); + psScriptInfoLines.Add(String.Format(".RELEASENOTES\n{0}", ReleaseNotes)); psScriptInfoLines.Add(String.Format(".PRIVATEDATA\n{0}", PrivateData)); psScriptInfoLines.Add("#>"); pSScriptInfoString = String.Join("\n\n", psScriptInfoLines); - return pSScriptInfoSuccessfullyCreated; + return true; } /// diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 7224de0ed..71e0a364e 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -107,7 +107,7 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet /// [Parameter] [ValidateNotNullOrEmpty()] - public string[] ReleaseNotes { get; set; } + public string ReleaseNotes { get; set; } /// /// Remove signature from signed .ps1 (if present) thereby allowing update of script to happen From dae5675258e62877bb2b44206447d004cd37d35e Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 7 Jul 2022 17:29:57 -0400 Subject: [PATCH 77/86] fix bug with ReleaseNotes, PrivateData being on next line than .KEY --- src/code/NewPSScriptFileInfo.cs | 2 +- src/code/PSScriptFileInfo.cs | 61 +++++++++++++++++++++++------- src/code/UpdatePSScriptFileInfo.cs | 2 +- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index ad6e17252..bf0e2f83a 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -128,7 +128,7 @@ public sealed class NewPSScriptFileInfo : PSCmdlet /// [Parameter] [ValidateNotNullOrEmpty()] - public string[] ReleaseNotes { get; set; } + public string ReleaseNotes { get; set; } /// /// The private data associated with the script diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index a99e14d0d..994b2a7b9 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -93,7 +93,7 @@ public sealed class PSScriptFileInfo /// /// the release notes relating to the script /// - public string[] ReleaseNotes { get; private set; } = new string[]{}; + public string ReleaseNotes { get; private set; } = String.Empty; /// /// The private data associated with the script @@ -180,7 +180,7 @@ public PSScriptFileInfo( string[] externalModuleDependencies, string[] requiredScripts, string[] externalScriptDependencies, - string[] releaseNotes, + string releaseNotes, string privateData, string description, string endOfFileContents @@ -204,7 +204,7 @@ string endOfFileContents ExternalModuleDependencies = externalModuleDependencies ?? Utils.EmptyStrArray; RequiredScripts = requiredScripts ?? Utils.EmptyStrArray; ExternalScriptDependencies = externalScriptDependencies ?? Utils.EmptyStrArray; - ReleaseNotes = releaseNotes ?? Utils.EmptyStrArray; + ReleaseNotes = releaseNotes; PrivateData = privateData; Description = description; EndOfFileContents = endOfFileContents; @@ -386,18 +386,48 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd return false; } + keyName = ""; + value = ""; + for (int i = 1; i < commentLines.Count(); i++) { string line = commentLines[i]; - // A line is starting with . conveys a new metadata property + // scenario where line is: .KEY VALUE + // this line contains a new metadata property if (line.Trim().StartsWith(".")) { - // .KEY VALUE + // 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; - parsedScriptMetadata.Add(keyName, value); + } + else if (!(line.Trim()).Equals("#>")) + { + // 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); + } } } @@ -521,11 +551,14 @@ internal static bool TryParseScriptIntoPSScriptInfo( string parsedCopyright = (string) parsedScriptMetadata["COPYRIGHT"] ?? String.Empty; string parsedPrivateData = (string) parsedScriptMetadata["PRIVATEDATA"] ?? String.Empty; + // string parsedReleaseNotes = (string) parsedScriptMetadata["RELEASENOTES"] ?? String.Empty; + // string trimmedReleaseNotes = parsedReleaseNotes.TrimStart('\n'); + 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"]); - string[] parsedReleaseNotes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["RELEASENOTES"]); ReadOnlyCollection parsedModules = (ReadOnlyCollection) parsedScriptMetadata["REQUIREDMODULES"] ?? new ReadOnlyCollection(new List()); @@ -613,7 +646,7 @@ internal static bool TryUpdateScriptFileContents( string[] externalModuleDependencies, string[] requiredScripts, string[] externalScriptDependencies, - string[] releaseNotes, + string releaseNotes, string privateData, string description) { @@ -692,7 +725,7 @@ internal static bool TryUpdateScriptFileContents( scriptInfo.ExternalScriptDependencies = externalScriptDependencies; } - if (releaseNotes != null && releaseNotes.Length != 0) + if (!String.IsNullOrEmpty(releaseNotes)) { scriptInfo.ReleaseNotes = releaseNotes; } @@ -818,8 +851,9 @@ out ErrorRecord error ) { error = null; - bool pSScriptInfoSuccessfullyCreated = false; - pSScriptInfoString = String.Empty; + // TODO: Anam does this really need to return bool? + // bool pSScriptInfoSuccessfullyCreated = false; + // pSScriptInfoString = String.Empty; /** PSScriptInfo comment will be in following format: @@ -847,7 +881,6 @@ Feature 5 #> */ - pSScriptInfoSuccessfullyCreated = true; List psScriptInfoLines = new List(); psScriptInfoLines.Add("<#PSScriptInfo"); @@ -863,12 +896,12 @@ Feature 5 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}", String.Join("\n", ReleaseNotes))); + psScriptInfoLines.Add(String.Format(".RELEASENOTES\n{0}", ReleaseNotes)); psScriptInfoLines.Add(String.Format(".PRIVATEDATA\n{0}", PrivateData)); psScriptInfoLines.Add("#>"); pSScriptInfoString = String.Join("\n\n", psScriptInfoLines); - return pSScriptInfoSuccessfullyCreated; + return true; } /// diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 7224de0ed..71e0a364e 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -107,7 +107,7 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet /// [Parameter] [ValidateNotNullOrEmpty()] - public string[] ReleaseNotes { get; set; } + public string ReleaseNotes { get; set; } /// /// Remove signature from signed .ps1 (if present) thereby allowing update of script to happen From 59eab7497ec487de4ebe6ca4b7f6fbff254ab3d3 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 7 Jul 2022 17:36:37 -0400 Subject: [PATCH 78/86] clean up code comments --- src/code/PSScriptFileInfo.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 994b2a7b9..156bd7abe 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -550,9 +550,6 @@ internal static bool TryParseScriptIntoPSScriptInfo( 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 trimmedReleaseNotes = parsedReleaseNotes.TrimStart('\n'); string parsedReleaseNotes = (string) parsedScriptMetadata["RELEASENOTES"] ?? String.Empty; string[] parsedTags = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedScriptMetadata["TAGS"]); From 2490dd4d6e1f5727f12e7f342d70a411be7494bf Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 8 Jul 2022 10:19:22 -0400 Subject: [PATCH 79/86] refactor commentLine parsing into helper method, and add . to /// lines --- src/code/NewPSScriptFileInfo.cs | 38 ++++---- src/code/PSScriptFileInfo.cs | 149 ++++++++++++++++++++--------- src/code/TestPSScriptFileInfo.cs | 2 +- src/code/UpdatePSScriptFileInfo.cs | 36 +++---- 4 files changed, 141 insertions(+), 84 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index bf0e2f83a..b85b2b24f 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { /// - /// Creates a new .ps1 file with script information required for publishing a script + /// Creates a new .ps1 file with script information required for publishing a script. /// [Cmdlet(VerbsCommon.New, "PSScriptFileInfo")] public sealed class NewPSScriptFileInfo : PSCmdlet @@ -19,126 +19,126 @@ public sealed class NewPSScriptFileInfo : PSCmdlet #region Parameters /// - /// The path the .ps1 script info file will be created at + /// 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 + /// The version of the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string Version { get; set; } /// - /// The author of the script + /// The author of the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string Author { get; set; } /// - /// The description of the script + /// The description of the script. /// [Parameter(Mandatory = true)] [ValidateNotNullOrEmpty()] public string Description { get; set; } /// - /// The GUID for the script + /// 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 + /// The name of the company owning the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string CompanyName { get; set; } /// - /// The copyright information for the script + /// The copyright statement for the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string Copyright { get; set; } /// - /// The list of modules required by the script + /// 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 + /// 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 + /// 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 + /// The list of external script dependencies taken by this script. /// [Parameter] [ValidateNotNullOrEmpty()] public string[] ExternalScriptDependencies { get; set; } /// - /// The tags associated with the script + /// The tags associated with the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string[] Tags { get; set; } /// - /// The Uri for the project associated with the script + /// 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 + /// 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 + /// The Uri for the icon associated with the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string IconUri { get; set; } /// - /// The release notes for the script + /// The release notes for the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string ReleaseNotes { get; set; } /// - /// The private data associated with the script + /// 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 + /// If used with Path parameter and .ps1 file specified at the path exists, it rewrites the file. /// [Parameter] public SwitchParameter Force { get; set; } diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 156bd7abe..d50ea5067 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -386,56 +386,59 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd return false; } - keyName = ""; - 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("#>")) - { - // 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); - } - } - } + GetMetadata(commentLines, out parsedScriptMetadata); + + // keyName = ""; + // 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("#>")) + // { + // // 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); + // } + // } + // } // get end of file contents string[] totalFileContents = File.ReadAllLines(scriptFileInfoPath); var contentAfterAndIncludingDescription = totalFileContents.SkipWhile(x => !x.Contains(".DESCRIPTION")).ToList(); - var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); - endOfFileContents = String.Join("\n", contentAfterDescription.ToArray()); + endOfFileContents = String.Join("\n", contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToArray()); + // var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); + // endOfFileContents = String.Join("\n", contentAfterDescription.ToArray()); return true; } @@ -1012,6 +1015,60 @@ out ErrorRecord error return psHelpInfoSuccessfullyCreated; } + /// + /// Helper method which takes lines of the PSScriptInfo comment block + /// and parses metadata from those lines into a hashtable + /// + private static void GetMetadata(string[] commentLines, out 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("#>")) + { + // 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) /// diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index ea363561d..88bdc64e5 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -19,7 +19,7 @@ public sealed class TestPSScriptFileInfo : PSCmdlet #region Parameters /// - /// The path to the .ps1 file to test + /// The path to the .ps1 file to test. /// [Parameter(Position = 0, Mandatory = true)] [ValidateNotNullOrEmpty] diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 71e0a364e..a8267c2de 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { /// - /// Updates a .ps1 file with specified properties + /// Updates a .ps1 file with specified properties. /// [Cmdlet(VerbsData.Update, "PSScriptFileInfo")] public sealed class UpdatePSScriptFileInfo : PSCmdlet @@ -19,91 +19,91 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet #region Parameters /// - /// The author of the script + /// The author of the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string Author { get; set; } /// - /// The name of the company owning the script + /// The name of the company owning the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string CompanyName { get; set; } /// - /// The copyright information for the script + /// The copyright statement for the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string Copyright { get; set; } /// - /// The description of the script + /// The description of the script. /// [Parameter()] [ValidateNotNullOrEmpty()] public string Description { get; set; } /// - /// The list of external module dependencies taken by this script + /// 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 + /// The list of external script dependencies taken by this script. /// [Parameter] [ValidateNotNullOrEmpty()] public string[] ExternalScriptDependencies { get; set; } /// - /// The GUID for the script + /// 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 + /// 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 + /// 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 + /// 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 + /// The private data associated with the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string PrivateData { get; set; } /// - /// The Uri for the project associated with the script + /// The Uri for the project associated with the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string ProjectUri { get; set; } /// - /// The release notes for the script + /// The release notes for the script. /// [Parameter] [ValidateNotNullOrEmpty()] @@ -117,28 +117,28 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet public SwitchParameter RemoveSignature { get; set; } /// - /// The list of modules required by the script + /// The list of modules required by the script. /// [Parameter] [ValidateNotNullOrEmpty()] public Hashtable[] RequiredModules { get; set; } /// - /// The list of scripts required by the script + /// The list of scripts required by the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string[] RequiredScripts { get; set; } /// - /// The tags associated with the script + /// The tags associated with the script. /// [Parameter] [ValidateNotNullOrEmpty()] public string[] Tags { get; set; } /// - /// The version of the script + /// The version of the script. /// [Parameter] [ValidateNotNullOrEmpty()] From bfcaa42ea8633a676a7bcac9fc47c3172b59bfbb Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 8 Jul 2022 10:30:29 -0400 Subject: [PATCH 80/86] code should be in EndProcessing() not ProcessRecord() --- src/code/NewPSScriptFileInfo.cs | 2 +- src/code/TestPSScriptFileInfo.cs | 2 +- src/code/UpdatePSScriptFileInfo.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index b85b2b24f..4a834a3c9 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -147,7 +147,7 @@ public sealed class NewPSScriptFileInfo : PSCmdlet #region Methods - protected override void ProcessRecord() + protected override void EndProcessing() { // validate Uri related parameters passed in as strings Uri projectUri = null; diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index 88bdc64e5..f02a052e3 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -29,7 +29,7 @@ public sealed class TestPSScriptFileInfo : PSCmdlet #region Methods - protected override void ProcessRecord() + protected override void EndProcessing() { if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index a8267c2de..d959c022c 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -154,7 +154,7 @@ public sealed class UpdatePSScriptFileInfo : PSCmdlet #region Methods - protected override void ProcessRecord() + protected override void EndProcessing() { Uri projectUri = null; if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri, From c21aecfcce855a862dab6db1d7b03c92d41b6793 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 8 Jul 2022 11:07:25 -0400 Subject: [PATCH 81/86] address PR feedback --- src/code/NewPSScriptFileInfo.cs | 4 +- src/code/PSScriptFileInfo.cs | 131 ++++++++++++++--------------- src/code/TestPSScriptFileInfo.cs | 3 + src/code/UpdatePSScriptFileInfo.cs | 3 + 4 files changed, 69 insertions(+), 72 deletions(-) diff --git a/src/code/NewPSScriptFileInfo.cs b/src/code/NewPSScriptFileInfo.cs index 4a834a3c9..8061688a1 100644 --- a/src/code/NewPSScriptFileInfo.cs +++ b/src/code/NewPSScriptFileInfo.cs @@ -220,7 +220,7 @@ protected override void EndProcessing() } } - PSScriptFileInfo currentScriptInfo = new PSScriptFileInfo( + PSScriptFileInfo scriptInfo = new PSScriptFileInfo( version: Version, guid: Guid, author: Author, @@ -239,7 +239,7 @@ protected override void EndProcessing() description: Description, endOfFileContents: String.Empty); - if (!currentScriptInfo.TryCreateScriptFileInfoString( + if (!scriptInfo.TryCreateScriptFileInfoString( pSScriptFileString: out string psScriptFileContents, errors: out ErrorRecord[] errors)) { diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index d50ea5067..78805a6a5 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -17,7 +17,7 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { /// - /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents) + /// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents). /// public sealed class PSScriptFileInfo { @@ -25,133 +25,133 @@ public sealed class PSScriptFileInfo #region Properties /// - /// the Version of the script + /// the version of the script. /// public NuGetVersion Version { get; private set; } /// - /// the GUID for the script + /// the GUID for the script. /// public Guid Guid { get; private set; } /// - /// the author for the script + /// the author for the script. /// public string Author { get; private set; } /// - /// the name of the company owning the script + /// the name of the company owning the script. /// public string CompanyName { get; private set; } /// - /// the copyright information for the script + /// the copyright statement for the script. /// public string Copyright { get; private set; } /// - /// the tags for the script + /// the tags for the script. /// public string[] Tags { get; private set; } /// - /// the Uri for the license of the script + /// the Uri for the license of the script. /// public Uri LicenseUri { get; private set; } /// - /// the Uri for the project relating to the script + /// the Uri for the project relating to the script. /// public Uri ProjectUri { get; private set; } /// - /// the Uri for the icon relating to the script + /// 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 + /// 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 + /// 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 + /// 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 + /// the list of external script dependencies for the script. /// public string[] ExternalScriptDependencies { get; private set; } = new string[]{}; /// - /// the release notes relating to the script + /// the release notes relating to the script. /// public string ReleaseNotes { get; private set; } = String.Empty; /// - /// The private data associated with the script + /// The private data associated with the script. /// public string PrivateData { get; private set; } /// - /// The description of the script + /// The description of the script. /// public string Description { get; private set; } /// - /// End of file contents for the .ps1 file + /// End of file contents for the .ps1 file. /// public string EndOfFileContents { get; private set; } = String.Empty; /// - /// The synopsis of the script + /// The synopsis of the script. /// public string Synopsis { get; private set; } /// - /// The example(s) relating to the script's usage + /// The example(s) relating to the script's usage. /// public string[] Example { get; private set; } = new string[]{}; /// - /// The inputs to the script + /// The inputs to the script. /// public string[] Inputs { get; private set; } = new string[]{}; /// - /// The outputs to the script + /// The outputs to the script. /// public string[] Outputs { get; private set; } = new string[]{}; /// - /// The notes for the script + /// The notes for the script. /// public string[] Notes { get; private set; } = new string[]{}; /// - /// The links for the script + /// The links for the script. /// public string[] Links { get; private set; } = new string[]{}; /// - /// The components for the script + /// The components for the script. /// public string[] Component { get; private set; } = new string[]{}; /// - /// The roles for the script + /// The roles for the script. /// public string[] Role { get; private set; } = new string[]{}; /// - /// The functionality for the + /// The functionality components for the script. /// public string[] Functionality { get; private set; } = new string[]{}; @@ -215,14 +215,13 @@ string endOfFileContents #region Internal Static Methods /// - /// Parses content of .ps1 file into a hashtable + /// Parses content of .ps1 file into a hashtable. /// - internal static bool TryParseScript( + internal static bool TryParseScriptFile( string scriptFileInfoPath, out Hashtable parsedScriptMetadata, out string endOfFileContents, - out ErrorRecord[] errors - ) + out ErrorRecord[] errors) { errors = new ErrorRecord[]{}; parsedScriptMetadata = new Hashtable(StringComparer.InvariantCultureIgnoreCase); @@ -437,19 +436,16 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd 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()); - // var contentAfterDescription = contentAfterAndIncludingDescription.SkipWhile(x => !x.Contains("#>")).Skip(1).ToList(); - // endOfFileContents = String.Join("\n", contentAfterDescription.ToArray()); return true; } /// - /// Takes hashtable (containing parsed .ps1 file content properties) and validates required properties are present + /// Takes hashtable (containing parsed .ps1 file content properties) and validates required properties are present. /// - internal static bool TryValidateScript( + private static bool TryValidateScript( Hashtable parsedScriptMetadata, - out ErrorRecord[] errors - ) + out ErrorRecord[] errors) { // required properties for script file (.ps1 file) are: Author, Version, Guid, Description // Description gets validated in TryParseScript() when getting the property @@ -481,16 +477,11 @@ out ErrorRecord[] errors } errors = errorsList.ToArray(); - if (errors.Length > 0) - { - return false; - } - - return true; + return (errors.Length == 0); } /// - /// Tests the contents of the .ps1 file at the provided path + /// Tests the contents of the .ps1 file at the provided path. /// internal static bool TryParseScriptIntoPSScriptInfo( string scriptFileInfoPath, @@ -514,7 +505,7 @@ internal static bool TryParseScriptIntoPSScriptInfo( return false; } - if (!TryParseScript( + if (!TryParseScriptFile( scriptFileInfoPath: scriptFileInfoPath, parsedScriptMetadata: out Hashtable parsedScriptMetadata, endOfFileContents: out string endofFileContents, @@ -570,7 +561,7 @@ internal static bool TryParseScriptIntoPSScriptInfo( { if (!Uri.TryCreate((string) parsedScriptMetadata["LICENSEURI"], UriKind.Absolute, out parsedLicenseUri)) { - verboseMsgsList.Add("LicenseUri property could not be created as a Uri"); + verboseMsgsList.Add($"LicenseUri property {(string) parsedScriptMetadata["LICENSEURI"]} could not be created as a Uri"); } } @@ -579,7 +570,7 @@ internal static bool TryParseScriptIntoPSScriptInfo( { if (!Uri.TryCreate((string) parsedScriptMetadata["PROJECTURI"], UriKind.Absolute, out parsedProjectUri)) { - verboseMsgsList.Add("ProjectUri property could not be created as Uri"); + verboseMsgsList.Add($"ProjectUri property {(string) parsedScriptMetadata["PROJECTURI"]} could not be created as Uri"); } } @@ -588,7 +579,7 @@ internal static bool TryParseScriptIntoPSScriptInfo( { if (!Uri.TryCreate((string) parsedScriptMetadata["ICONURI"], UriKind.Absolute, out parsedIconUri)) { - verboseMsgsList.Add("IconUri property could not be created as Uri"); + verboseMsgsList.Add($"IconUri property {(string) parsedScriptMetadata["ICONURI"]} could not be created as Uri"); } } @@ -627,7 +618,7 @@ internal static bool TryParseScriptIntoPSScriptInfo( /// /// 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 + /// and writes new updated script file contents to a string and updates the original PSScriptFileInfo object. /// internal static bool TryUpdateScriptFileContents( PSScriptFileInfo scriptInfo, @@ -760,13 +751,11 @@ internal static bool TryUpdateScriptFileContents( /// /// 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 + /// end of file contents are not yet added to the string contents of the file. /// internal bool TryCreateScriptFileInfoString( - // string filePath, out string pSScriptFileString, // this is the string with the contents we want to put in the new ps1 file - out ErrorRecord[] errors - ) + out ErrorRecord[] errors) { errors = new ErrorRecord[]{}; List errorsList = new List(); @@ -820,7 +809,6 @@ out ErrorRecord[] errors // 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 @@ -841,14 +829,14 @@ out ErrorRecord[] errors #endregion #region Private Methods + /// /// Used when creating .ps1 file's contents. - /// This creates the <#PSScriptInfo ... #> comment string + /// This creates the <#PSScriptInfo ... #> comment string. /// private bool GetPSScriptInfoString( out string pSScriptInfoString, - out ErrorRecord error - ) + out ErrorRecord error) { error = null; // TODO: Anam does this really need to return bool? @@ -906,11 +894,9 @@ Feature 5 /// /// Used when creating .ps1 file's contents. - /// This creates the #Requires comment string + /// This creates the #Requires comment string. /// - private void GetRequiresString( - out string psRequiresString - ) + private void GetRequiresString(out string psRequiresString) { psRequiresString = String.Empty; @@ -934,8 +920,7 @@ out string psRequiresString /// private bool GetScriptCommentHelpInfo( out string psHelpInfo, - out ErrorRecord error - ) + out ErrorRecord error) { error = null; psHelpInfo = String.Empty; @@ -1017,9 +1002,11 @@ out ErrorRecord error /// /// Helper method which takes lines of the PSScriptInfo comment block - /// and parses metadata from those lines into a hashtable + /// and parses metadata from those lines into a hashtable. /// - private static void GetMetadata(string[] commentLines, out Hashtable parsedScriptMetadata) + private static void GetMetadata( + string[] commentLines, + out Hashtable parsedScriptMetadata) { parsedScriptMetadata = new Hashtable(); string keyName = ""; @@ -1030,7 +1017,7 @@ private static void GetMetadata(string[] commentLines, out Hashtable parsedScrip string line = commentLines[i]; // scenario where line is: .KEY VALUE - // this line contains a new metadata property + // 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 @@ -1046,7 +1033,7 @@ private static void GetMetadata(string[] commentLines, out Hashtable parsedScrip else if (!(line.Trim()).Equals("#>")) { // 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 + // this line does not starting with .KEY, and is also not an empty line. if (value.Equals(String.Empty)) { value += line; @@ -1059,7 +1046,7 @@ private static void GetMetadata(string[] commentLines, out Hashtable parsedScrip else { // scenario where line is: #> - // this line signifies end of comment block, so add last recorded key value pair before the comment block ends + // 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); @@ -1070,13 +1057,17 @@ private static void GetMetadata(string[] commentLines, out Hashtable parsedScrip } /// - /// Ensure no fields (passed as stringToValidate) contains '<#' or '#>' (would break AST block section) + /// 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() { int signatureStartIndex = EndOfFileContents.IndexOf(signatureStartString); diff --git a/src/code/TestPSScriptFileInfo.cs b/src/code/TestPSScriptFileInfo.cs index f02a052e3..09a1448bb 100644 --- a/src/code/TestPSScriptFileInfo.cs +++ b/src/code/TestPSScriptFileInfo.cs @@ -75,6 +75,9 @@ protected override void EndProcessing() foreach (string msg in verboseMsgs) { WriteVerbose(msg); + + // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. + WriteWarning(msg); } WriteObject(isValidScript); diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index d959c022c..2586987b3 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -236,6 +236,9 @@ protected override void EndProcessing() foreach (string msg in verboseMsgs) { WriteVerbose(msg); + + // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. + WriteWarning(msg); } WriteWarning("The .ps1 script file passed in was not valid due to the following error(s) listed below"); From 7600d14d956024d1f96d1f55dd993dbcfc919690 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 8 Jul 2022 11:09:53 -0400 Subject: [PATCH 82/86] address more PR feedback --- src/code/UpdatePSScriptFileInfo.cs | 4 ++-- src/code/Utils.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/code/UpdatePSScriptFileInfo.cs b/src/code/UpdatePSScriptFileInfo.cs index 2586987b3..6db705283 100644 --- a/src/code/UpdatePSScriptFileInfo.cs +++ b/src/code/UpdatePSScriptFileInfo.cs @@ -236,7 +236,7 @@ protected override void EndProcessing() foreach (string msg in verboseMsgs) { WriteVerbose(msg); - + // also write a warning as the existing ProjectUri, LicenseUri, IconUri may be overwrriten if they were determined to not be valid when parsed. WriteWarning(msg); } @@ -255,7 +255,7 @@ protected override void EndProcessing() WriteWarning("This script contains a signature and cannot be updated without invalidating the current script signature"); if (!RemoveSignature) { - var exMessage = "Cannot update script as the .ps1 contains a signature. Either use -RemoveSignature paramter or manaully remove signature block and re-run cmdlet."; + 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); diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 2f32502b4..cd42d4f7c 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -120,7 +120,7 @@ public static string[] GetStringArrayFromString(char[] delimeter, string stringT // this will be a string where entries are separated by space if (String.IsNullOrEmpty(stringToConvertToArray)) { - return new string[]{}; + return Utils.EmptyStrArray; } return stringToConvertToArray.Split(delimeter, StringSplitOptions.RemoveEmptyEntries); From 6bdbb8607667d5389ceb9613d0a532298ac77f03 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 8 Jul 2022 14:25:50 -0400 Subject: [PATCH 83/86] fix bug with Description property being overriden in metadata hashtable --- src/code/PSScriptFileInfo.cs | 74 ++++++++---------------------------- 1 file changed, 16 insertions(+), 58 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 78805a6a5..8d45618c7 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -183,8 +183,7 @@ public PSScriptFileInfo( string releaseNotes, string privateData, string description, - string endOfFileContents - ) + string endOfFileContents) { if (String.IsNullOrEmpty(author)) { @@ -385,52 +384,7 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd return false; } - GetMetadata(commentLines, out parsedScriptMetadata); - - // keyName = ""; - // 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("#>")) - // { - // // 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); - // } - // } - // } + GetMetadataFromCommentLines(commentLines, ref parsedScriptMetadata); // get end of file contents string[] totalFileContents = File.ReadAllLines(scriptFileInfoPath); @@ -814,10 +768,11 @@ internal bool TryCreateScriptFileInfoString( // have a script being updated, and contains no Signature, or contains a Signature but -RemoveSignature was used with cmdlet if (!String.IsNullOrEmpty(EndOfFileContents)) { - if (EndOfFileContents.Contains(signatureStartString)) - { - RemoveSignatureString(); - } + RemoveSignatureString(); + // if (EndOfFileContents.Contains(signatureStartString)) + // { + // RemoveSignatureString(); + // } pSScriptFileString += "\n" + EndOfFileContents; } @@ -829,7 +784,7 @@ internal bool TryCreateScriptFileInfoString( #endregion #region Private Methods - + /// /// Used when creating .ps1 file's contents. /// This creates the <#PSScriptInfo ... #> comment string. @@ -1004,11 +959,11 @@ private bool GetScriptCommentHelpInfo( /// Helper method which takes lines of the PSScriptInfo comment block /// and parses metadata from those lines into a hashtable. /// - private static void GetMetadata( + private static void GetMetadataFromCommentLines( string[] commentLines, - out Hashtable parsedScriptMetadata) + ref Hashtable parsedScriptMetadata) { - parsedScriptMetadata = new Hashtable(); + // parsedScriptMetadata = new Hashtable(); string keyName = ""; string value = ""; @@ -1070,8 +1025,11 @@ private bool StringContainsComment(string stringToValidate) /// private void RemoveSignatureString() { - int signatureStartIndex = EndOfFileContents.IndexOf(signatureStartString); - EndOfFileContents = EndOfFileContents.Substring(0, signatureStartIndex); + if (EndOfFileContents.Contains(signatureStartString)) + { + int signatureStartIndex = EndOfFileContents.IndexOf(signatureStartString); + EndOfFileContents = EndOfFileContents.Substring(0, signatureStartIndex); + } } #endregion From e9765e01ca2031c4ce7950a5a2484dfd5adf161f Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 8 Jul 2022 14:47:28 -0400 Subject: [PATCH 84/86] fix todo --- src/code/PSScriptFileInfo.cs | 67 ++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs index 8d45618c7..faeeb9f8c 100644 --- a/src/code/PSScriptFileInfo.cs +++ b/src/code/PSScriptFileInfo.cs @@ -381,7 +381,7 @@ .GUID 3951be04-bd06-4337-8dc3-a620bf539fbd errorsList.Add(psScriptInfoCommentEmptyError); errors = errorsList.ToArray(); - return false; + return false; } GetMetadataFromCommentLines(commentLines, ref parsedScriptMetadata); @@ -721,12 +721,11 @@ internal bool TryCreateScriptFileInfoString( // this can only have one error (i.e Author or Version is missing) if (!GetPSScriptInfoString( pSScriptInfoString: out string psScriptInfoCommentString, - out ErrorRecord scriptInfoError)) + out ErrorRecord[] scriptInfoErrors)) { - if (scriptInfoError != null) + if (scriptInfoErrors.Length != 0) { - errorsList.Add(scriptInfoError); - errors = errorsList.ToArray(); + errors = scriptInfoErrors.ToArray(); } return fileContentsSuccessfullyCreated; @@ -769,11 +768,6 @@ internal bool TryCreateScriptFileInfoString( if (!String.IsNullOrEmpty(EndOfFileContents)) { RemoveSignatureString(); - // if (EndOfFileContents.Contains(signatureStartString)) - // { - // RemoveSignatureString(); - // } - pSScriptFileString += "\n" + EndOfFileContents; } @@ -791,12 +785,11 @@ internal bool TryCreateScriptFileInfoString( /// private bool GetPSScriptInfoString( out string pSScriptInfoString, - out ErrorRecord error) + out ErrorRecord[] errors) { - error = null; - // TODO: Anam does this really need to return bool? - // bool pSScriptInfoSuccessfullyCreated = false; - // pSScriptInfoString = String.Empty; + List errorsList = new List(); + bool pSScriptInfoSuccessfullyCreated = false; + pSScriptInfoString = String.Empty; /** PSScriptInfo comment will be in following format: @@ -824,6 +817,49 @@ Feature 5 #> */ + 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"); @@ -844,6 +880,7 @@ Feature 5 psScriptInfoLines.Add("#>"); pSScriptInfoString = String.Join("\n\n", psScriptInfoLines); + errors = errorsList.ToArray(); return true; } From bf9b21bd2217486804a026fc3243a3ed19cebaf4 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 11 Jul 2022 12:02:04 -0400 Subject: [PATCH 85/86] Pester TestDrive will clean up folders created there, don't do ourselves in AfterAll block --- test/NewPSScriptFileInfo.Tests.ps1 | 5 ----- test/TestPSScriptFileInfo.Tests.ps1 | 12 +----------- test/UpdatePSScriptFileInfo.Tests.ps1 | 6 ------ 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/test/NewPSScriptFileInfo.Tests.ps1 b/test/NewPSScriptFileInfo.Tests.ps1 index ae89b2fcf..727a05792 100644 --- a/test/NewPSScriptFileInfo.Tests.ps1 +++ b/test/NewPSScriptFileInfo.Tests.ps1 @@ -19,11 +19,6 @@ Describe "Test New-PSScriptFileInfo" { Remove-Item $script:testScriptFilePath } } - AfterAll { - $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" - $tmpDirPaths = @($tmpDir1Path) - Get-RemoveTestDirs($tmpDirPaths) - } It "Create .ps1 file with minimal required fields" { $description = "Test description" diff --git a/test/TestPSScriptFileInfo.Tests.ps1 b/test/TestPSScriptFileInfo.Tests.ps1 index 454a8aa14..30adae47b 100644 --- a/test/TestPSScriptFileInfo.Tests.ps1 +++ b/test/TestPSScriptFileInfo.Tests.ps1 @@ -6,9 +6,7 @@ Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force Describe "Test Test-PSScriptFileInfo" { BeforeAll { $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" - $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" - $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" - $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) + $tmpDirPaths = @($tmpDir1Path) Get-NewTestDirs($tmpDirPaths) # Path to folder, within our test folder, where we store invalid module and script files used for testing @@ -18,14 +16,6 @@ Describe "Test Test-PSScriptFileInfo" { $script:testScriptsFolderPath = Join-Path $testFilesFolderPath -ChildPath "testScripts" } - AfterAll { - $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" - $tmpDir2Path = Join-Path -Path $TestDrive -ChildPath "tmpDir2" - $tmpDir3Path = Join-Path -Path $TestDrive -ChildPath "tmpDir3" - $tmpDirPaths = @($tmpDir1Path, $tmpDir2Path, $tmpDir3Path) - Get-RemoveTestDirs($tmpDirPaths) - } - It "determine script file with minimal required fields as valid" { $scriptFilePath = Join-Path -Path $tmpDir1Path -ChildPath "testscript.ps1" $scriptDescription = "this is a test script" diff --git a/test/UpdatePSScriptFileInfo.Tests.ps1 b/test/UpdatePSScriptFileInfo.Tests.ps1 index 2d1b76d7e..767ca638c 100644 --- a/test/UpdatePSScriptFileInfo.Tests.ps1 +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -30,12 +30,6 @@ Describe "Test Update-PSScriptFileInfo" { } } - AfterAll { - $tmpDir1Path = Join-Path -Path $TestDrive -ChildPath "tmpDir1" - $tmpDirPaths = @($tmpDir1Path) - Get-RemoveTestDirs($tmpDirPaths) - } - It "Update .ps1 file with relative path" { $relativeCurrentPath = Get-Location $scriptFilePath = Join-Path -Path $relativeCurrentPath -ChildPath "$script:psScriptInfoName.ps1" From 5d1f1a7c49ba2269bfb1940b8a44beeeb979de47 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 11 Jul 2022 12:48:24 -0400 Subject: [PATCH 86/86] add test to Update to ensure old data is not overwritten by update --- test/UpdatePSScriptFileInfo.Tests.ps1 | 38 ++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/test/UpdatePSScriptFileInfo.Tests.ps1 b/test/UpdatePSScriptFileInfo.Tests.ps1 index 767ca638c..91d59bbcd 100644 --- a/test/UpdatePSScriptFileInfo.Tests.ps1 +++ b/test/UpdatePSScriptFileInfo.Tests.ps1 @@ -38,9 +38,45 @@ Describe "Test Update-PSScriptFileInfo" { 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 - Remove-Item -Path $scriptFilePath + $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" {