diff --git a/src/code/PSScriptContents.cs b/src/code/PSScriptContents.cs
index c04f1dc49..63f1c4e80 100644
--- a/src/code/PSScriptContents.cs
+++ b/src/code/PSScriptContents.cs
@@ -15,7 +15,7 @@ public sealed class PSScriptContents
///
/// End of file contents for the .ps1 file.
///
- public string[] EndOfFileContents { get; private set; } = Utils.EmptyStrArray;
+ public string[] ScriptContents { get; private set; } = Utils.EmptyStrArray;
///
/// End of file contents for the .ps1 file.
@@ -38,7 +38,7 @@ public sealed class PSScriptContents
///
public PSScriptContents(string[] endOfFileContents)
{
- EndOfFileContents = endOfFileContents;
+ ScriptContents = endOfFileContents;
ContainsSignature = CheckForSignature();
}
@@ -60,7 +60,7 @@ internal void ParseContent(string[] commentLines)
{
if (commentLines.Length != 0)
{
- EndOfFileContents = commentLines;
+ ScriptContents = commentLines;
ContainsSignature = CheckForSignature();
}
}
@@ -74,7 +74,7 @@ internal void ParseContent(string[] commentLines)
internal string[] EmitContent()
{
RemoveSignatureString();
- return EndOfFileContents;
+ return ScriptContents;
}
#endregion
@@ -86,11 +86,12 @@ internal string[] EmitContent()
///
private bool CheckForSignature()
{
- for (int i = 0; i < EndOfFileContents.Length; i++)
+ for (int i = 0; i < ScriptContents.Length; i++)
{
- if (String.Equals(EndOfFileContents[i], signatureStartString, StringComparison.InvariantCultureIgnoreCase))
+ if (String.Equals(ScriptContents[i], signatureStartString, StringComparison.InvariantCultureIgnoreCase))
{
_signatureStartIndex = i;
+ break;
}
}
@@ -105,9 +106,13 @@ private void RemoveSignatureString()
{
if (ContainsSignature)
{
- string[] newEndOfFileContents = new string[EndOfFileContents.Length - _signatureStartIndex];
- Array.Copy(EndOfFileContents, newEndOfFileContents, _signatureStartIndex);
- EndOfFileContents = newEndOfFileContents;
+ // The script signature comment block always appears at the end of the script file,
+ // so its start location becomes the end of the content section after the signature
+ // comment block is removed, and is also the length of the content section minus the
+ // signature block.
+ string[] contentsWithoutSignature = new string[_signatureStartIndex];
+ Array.Copy(ScriptContents, contentsWithoutSignature, _signatureStartIndex);
+ ScriptContents = contentsWithoutSignature;
ContainsSignature = false;
}
diff --git a/src/code/PSScriptFileInfo.cs b/src/code/PSScriptFileInfo.cs
index f3c4f3b5b..dfeee2121 100644
--- a/src/code/PSScriptFileInfo.cs
+++ b/src/code/PSScriptFileInfo.cs
@@ -132,14 +132,15 @@ internal static bool TryParseScriptFileContents(
while (j < fileContents.Length)
{
string blockLine = fileContents[j];
+ psScriptInfoCommentContent.Add(blockLine);
if (blockLine.StartsWith("#>"))
{
+
reachedPSScriptInfoCommentEnd = true;
i = j + 1;
break;
}
- psScriptInfoCommentContent.Add(blockLine);
j++;
}
@@ -159,6 +160,7 @@ internal static bool TryParseScriptFileContents(
while (j < fileContents.Length)
{
string blockLine = fileContents[j];
+
if (blockLine.StartsWith("#>"))
{
reachedHelpInfoCommentEnd = true;
@@ -166,7 +168,7 @@ internal static bool TryParseScriptFileContents(
endOfFileContentsStartIndex = i;
break;
}
-
+
helpInfoCommentContent.Add(blockLine);
j++;
}
diff --git a/src/code/PSScriptHelp.cs b/src/code/PSScriptHelp.cs
index 9964d2159..d6cef24ba 100644
--- a/src/code/PSScriptHelp.cs
+++ b/src/code/PSScriptHelp.cs
@@ -18,52 +18,12 @@ public sealed class PSScriptHelp
///
/// The description of the script.
///
- public string Description { get; private set; }
+ public string Description { get; private set; } = String.Empty;
///
- /// The synopsis of the script.
+ /// This contains all help content aside from Description
///
- public string Synopsis { get; private set; }
-
- ///
- /// The example(s) relating to the script's usage.
- ///
- public string[] Example { get; private set; } = Utils.EmptyStrArray;
-
- ///
- /// The inputs to the script.
- ///
- public string[] Inputs { get; private set; } = Utils.EmptyStrArray;
-
- ///
- /// The outputs to the script.
- ///
- public string[] Outputs { get; private set; } = Utils.EmptyStrArray;
-
- ///
- /// The notes for the script.
- ///
- public string[] Notes { get; private set; } = Utils.EmptyStrArray;
-
- ///
- /// The links for the script.
- ///
- public string[] Links { get; private set; } = Utils.EmptyStrArray;
-
- ///
- /// The components for the script.
- ///
- public string[] Component { get; private set; } = Utils.EmptyStrArray;
-
- ///
- /// The roles for the script.
- ///
- public string[] Role { get; private set; } = Utils.EmptyStrArray;
-
- ///
- /// The functionality components for the script.
- ///
- public string[] Functionality { get; private set; } = Utils.EmptyStrArray;
+ public List HelpContent { get; private set; } = new List();
#endregion
@@ -74,35 +34,7 @@ public sealed class PSScriptHelp
///
public PSScriptHelp (string description)
{
- this.Description = description;
- }
-
- ///
- /// This constructor takes values for description as well as other properties and creates a new PSScriptHelp instance.
- /// Currently, the New-PSScriptFileInfo and Update-PSScriptFileInfo cmdlets don't support the user providing these values.
- ///
- public PSScriptHelp (
- string description,
- string synopsis,
- string[] example,
- string[] inputs,
- string[] outputs,
- string[] notes,
- string[] links,
- string[] component,
- string[] role,
- string[] functionality)
- {
- this.Description = description;
- this.Synopsis = synopsis;
- this.Example = example;
- this.Inputs = inputs;
- this.Outputs = outputs;
- this.Notes = notes;
- this.Links = links;
- this.Component = component;
- this.Role = role;
- this.Functionality = functionality;
+ Description = description;
}
///
@@ -121,33 +53,91 @@ internal PSScriptHelp() {}
///
internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord error)
{
- bool successfullyParsed = true;
- string[] spaceDelimeter = new string[]{" "};
- string[] newlineDelimeter = new string[]{Environment.NewLine};
+ error = null;
- // parse content into a hashtable
- Hashtable parsedHelpMetadata = Utils.ParseCommentBlockContent(commentLines);
+ // Parse content into a hashtable.
+ Hashtable parsedHelpMetadata = ParseHelpContentHelper(commentLines);
- if (!ValidateParsedContent(parsedHelpMetadata, out error))
+ if (!ValidateParsedContent(parsedHelpMetadata, out ErrorRecord validationError))
{
+ error = validationError;
return false;
}
- // populate object
- Description = (string) parsedHelpMetadata["DESCRIPTION"];
- Synopsis = (string) parsedHelpMetadata["SYNOPSIS"] ?? String.Empty;
- Example = Utils.GetStringArrayFromString(newlineDelimeter, (string) parsedHelpMetadata["EXAMPLE"]);
- Inputs = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["INPUT"]);
- Outputs = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["OUTPUTS"]);
- Notes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["NOTES"]);
- Links = Utils.GetStringArrayFromString(newlineDelimeter, (string) parsedHelpMetadata["LINKS"]);
- Component = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["COMPONENT"]);
- Role = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["ROLE"]);
- Functionality = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["FUNCTIONALITY"]);
-
- return successfullyParsed;
+ // Populate object.
+ List descriptionValue = (List) parsedHelpMetadata["DESCRIPTION"];
+ Description = String.Join(Environment.NewLine, descriptionValue);
+ if (parsedHelpMetadata.ContainsKey("HELPCONTENT"))
+ {
+ HelpContent = (List) parsedHelpMetadata["HELPCONTENT"];
+ }
+
+ return true;
}
+ ///
+ /// Parses metadata out of PSScriptCommentInfo comment block's lines (which are passed in) into a hashtable.
+ ///
+ public static Hashtable ParseHelpContentHelper(string[] commentLines)
+ {
+ /**
+ Comment lines can look like this:
+
+ .KEY1 value
+
+ .KEY2 value
+
+ .KEY2 value2
+
+ .KEY3
+ value
+
+ .KEY4 value
+ value continued
+
+ */
+
+ // Parse out Description and everything else into a bucket list.
+
+ List helpContent = new List();
+ List descriptionValue = new List();
+ bool parsingDescription = false;
+
+ for(int i = 0; i < commentLines.Length; i++)
+ {
+ string line = commentLines[i];
+ if (line.Trim().StartsWith(".DESCRIPTION"))
+ {
+ parsingDescription = true;
+ }
+ else if (line.Trim().StartsWith("."))
+ {
+ parsingDescription = false;
+ helpContent.Add(line);
+ }
+ else if (!String.IsNullOrEmpty(line))
+ {
+ if (parsingDescription)
+ {
+ descriptionValue.Add(line);
+ }
+ else
+ {
+ helpContent.Add(line);
+ }
+ }
+ }
+
+ Hashtable parsedHelpMetadata = new Hashtable();
+ parsedHelpMetadata.Add("DESCRIPTION", descriptionValue);
+ if (helpContent.Count != 0)
+ {
+ parsedHelpMetadata.Add("HELPCONTENT", helpContent);
+ }
+
+ return parsedHelpMetadata;
+ }
+
///
/// Valides parsed help info content from the hashtable to ensure required help metadata (Description) is present
/// and does not contain empty values.
@@ -155,7 +145,7 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord error)
internal bool ValidateParsedContent(Hashtable parsedHelpMetadata, out ErrorRecord error)
{
error = null;
- if (!parsedHelpMetadata.ContainsKey("DESCRIPTION") || String.IsNullOrEmpty((string) parsedHelpMetadata["DESCRIPTION"]) || String.Equals(((string) parsedHelpMetadata["DESCRIPTION"]).Trim(), String.Empty))
+ if (!parsedHelpMetadata.ContainsKey("DESCRIPTION"))
{
var exMessage = "PSScript file must contain value for Description. Ensure value for Description is passed in and try again.";
var ex = new ArgumentException(exMessage);
@@ -164,7 +154,18 @@ internal bool ValidateParsedContent(Hashtable parsedHelpMetadata, out ErrorRecor
return false;
}
- if (StringContainsComment((string) parsedHelpMetadata["DESCRIPTION"]))
+ List descriptionValue = (List) parsedHelpMetadata["DESCRIPTION"];
+ string descriptionString = String.Join("", descriptionValue);
+ if (descriptionValue.Count == 0 || (String.IsNullOrEmpty(descriptionString)) || String.IsNullOrWhiteSpace(descriptionString))
+ {
+ var exMessage = "PSScript file value for Description cannot be null, empty or whitespace. Ensure value for Description meets these conditions and try again.";
+ var ex = new ArgumentException(exMessage);
+ var PSScriptInfoMissingDescriptionError = new ErrorRecord(ex, "PSScriptInfoMissingDescription", ErrorCategory.InvalidArgument, null);
+ error = PSScriptInfoMissingDescriptionError;
+ return false;
+ }
+
+ if (StringContainsComment(descriptionString))
{
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);
@@ -216,60 +217,11 @@ internal string[] EmitContent()
psHelpInfoLines.Add($".DESCRIPTION");
psHelpInfoLines.Add($"{Description}{Environment.NewLine}");
- if (!String.IsNullOrEmpty(Synopsis))
- {
- psHelpInfoLines.Add($".SYNOPSIS");
- psHelpInfoLines.Add($"{Synopsis}{Environment.NewLine}");
- }
-
- foreach (string currentExample in Example)
- {
- psHelpInfoLines.Add($".EXAMPLE");
- psHelpInfoLines.Add($"{currentExample}{Environment.NewLine}");
- }
-
- foreach (string input in Inputs)
- {
- psHelpInfoLines.Add($".INPUTS");
- psHelpInfoLines.Add($"{input}{Environment.NewLine}");
- }
-
- foreach (string output in Outputs)
- {
- psHelpInfoLines.Add($".OUTPUTS");
- psHelpInfoLines.Add($"{output}{Environment.NewLine}");
- }
-
- if (Notes.Length > 0)
+ if (HelpContent.Count != 0)
{
- psHelpInfoLines.Add($".NOTES");
- psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Notes)}{Environment.NewLine}");
- }
-
- foreach (string link in Links)
- {
- psHelpInfoLines.Add($".LINK");
- psHelpInfoLines.Add($"{link}{Environment.NewLine}");
- }
-
- if (Component.Length > 0)
- {
- psHelpInfoLines.Add($".COMPONENT");
- psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Component)}{Environment.NewLine}");
+ psHelpInfoLines.AddRange(HelpContent);
}
- if (Role.Length > 0)
- {
- psHelpInfoLines.Add($".ROLE");
- psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Role)}{Environment.NewLine}");
- }
-
- if (Functionality.Length > 0)
- {
- psHelpInfoLines.Add($".FUNCTIONALITY");
- psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Functionality)}{Environment.NewLine}");
- }
-
psHelpInfoLines.Add("#>");
return psHelpInfoLines.ToArray();
diff --git a/src/code/PSScriptMetadata.cs b/src/code/PSScriptMetadata.cs
index ded8088e7..f5cadb1ed 100644
--- a/src/code/PSScriptMetadata.cs
+++ b/src/code/PSScriptMetadata.cs
@@ -150,7 +150,12 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error
List msgsList = new List();
// parse content into a hashtable
- Hashtable parsedMetadata = Utils.ParseCommentBlockContent(commentLines);
+ Hashtable parsedMetadata = ParseMetadataContentHelper(commentLines, out errors);
+ if (errors.Length != 0)
+ {
+ return false;
+ }
+
if (parsedMetadata.Count == 0)
{
@@ -220,6 +225,92 @@ internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord[] error
return true;
}
+ ///
+ /// Parses metadata out of PSScriptCommentInfo comment block's lines (which are passed in) into a hashtable.
+ /// This comment block cannot have duplicate keys.
+ ///
+ public static Hashtable ParseMetadataContentHelper(string[] commentLines, out ErrorRecord[] errors)
+ {
+ /**
+ Comment lines can look like this:
+
+ .KEY1 value
+
+ .KEY2 value
+
+ .KEY3
+ value
+
+ .KEY4 value
+ value continued
+
+ */
+
+ errors = Array.Empty();
+ List errorsList = new List();
+
+ Hashtable parsedHelpMetadata = new Hashtable();
+ char[] spaceDelimeter = new char[]{' '};
+ string keyName = "";
+ string value = "";
+
+ for (int i = 1; i < commentLines.Length; 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))
+ {
+ if (parsedHelpMetadata.ContainsKey(keyName))
+ {
+ var message = String.Format("PowerShell script '<#PSScriptInfo .. #>' comment block metadata cannot contain duplicate key i.e .KEY");
+ var ex = new InvalidOperationException(message);
+ var psScriptInfoDuplicateKeyError = new ErrorRecord(ex, "psScriptInfoDuplicateKeyError", ErrorCategory.ParserError, null);
+ errorsList.Add(psScriptInfoDuplicateKeyError);
+ continue;
+ }
+
+ parsedHelpMetadata.Add(keyName, value);
+ }
+
+ // setting count to 2 will get 1st separated string (key) into part[0] and the rest (value) into part[1] if any
+ string[] parts = line.Trim().TrimStart('.').Split(separator: spaceDelimeter, count: 2);
+ keyName = parts[0];
+ value = parts.Length == 2 ? parts[1] : String.Empty;
+ }
+ else if (line.Trim().StartsWith("#>"))
+ {
+ // This line signifies end of comment block, so add last recorded key value pair before the comment block ends.
+ if (!String.IsNullOrEmpty(keyName) && !parsedHelpMetadata.ContainsKey(keyName))
+ {
+ // only add this key value if it hasn't already been added
+ parsedHelpMetadata.Add(keyName, value);
+ }
+ }
+ else if (!String.IsNullOrEmpty(line))
+ {
+ // scenario where line contains text that is a continuation of value from previously recorded key
+ // this line does not starting with .KEY, and is also not an empty line.
+ if (value.Equals(String.Empty))
+ {
+ value += line;
+ }
+ else
+ {
+ value += Environment.NewLine + line;
+ }
+ }
+ }
+
+ errors = errorsList.ToArray();
+
+ return parsedHelpMetadata;
+ }
+
///
/// Valides parsed metadata content from the hashtable to ensure required metadata (Author, Version, Guid) is present
/// and does not contain empty values.
diff --git a/src/code/Utils.cs b/src/code/Utils.cs
index 977d0be8a..32292d87f 100644
--- a/src/code/Utils.cs
+++ b/src/code/Utils.cs
@@ -1037,74 +1037,6 @@ public static bool TryCreateModuleSpecification(
return moduleSpecCreatedSuccessfully;
}
- ///
- /// Parses metadata out of a comment block's lines (which are passed in) into a hashtable.
- ///
- public static Hashtable ParseCommentBlockContent(string[] commentLines)
- {
- /**
- Comment lines can look like this:
-
- .KEY1 value
-
- .KEY2 value
-
- .KEY3
- value
-
- .KEY4 value
- value continued
-
- */
-
- Hashtable parsedHelpMetadata = new Hashtable();
- string keyName = "";
- string value = "";
-
- for (int i = 1; i < commentLines.Count(); i++)
- {
- string line = commentLines[i];
-
- // scenario where line is: .KEY VALUE
- // this line contains a new metadata property.
- if (line.Trim().StartsWith("."))
- {
- // check if keyName was previously populated, if so add this key value pair to the metadata hashtable
- if (!String.IsNullOrEmpty(keyName))
- {
- parsedHelpMetadata.Add(keyName, value);
- }
-
- string[] parts = line.Trim().TrimStart('.').Split();
- keyName = parts[0];
- value = parts.Count() > 1 ? String.Join(" ", parts.Skip(1)) : String.Empty;
- }
- else if (!String.IsNullOrEmpty(line))
- {
- // scenario where line contains text that is a continuation of value from previously recorded key
- // this line does not starting with .KEY, and is also not an empty line.
- if (value.Equals(String.Empty))
- {
- value += line;
- }
- else
- {
- value += Environment.NewLine + line;
- }
- }
- }
-
- // this is the case where last key value had multi-line value.
- // and we've captured it, but still need to add it to hashtable.
- if (!String.IsNullOrEmpty(keyName) && !parsedHelpMetadata.ContainsKey(keyName))
- {
- // only add this key value if it hasn't already been added
- parsedHelpMetadata.Add(keyName, value);
- }
-
- return parsedHelpMetadata;
- }
-
#endregion
#region Directory and File