Skip to content

Commit c07181c

Browse files
authored
Script info cmdlets with Line parsing and class implementations (#708)
Script info cmdlets with Line parsing and class implementations
1 parent d6433ba commit c07181c

16 files changed

+2990
-664
lines changed

src/PowerShellGet.psd1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
'Register-PSResourceRepository',
2222
'Save-PSResource',
2323
'Set-PSResourceRepository',
24+
'New-PSScriptFileInfo',
25+
'Test-PSScriptFileInfo',
26+
'Update-PSScriptFileInfo',
2427
'Publish-PSResource',
2528
'Uninstall-PSResource',
2629
'Unregister-PSResourceRepository',

src/code/NewPSScriptFileInfo.cs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections;
6+
using System.IO;
7+
using System.Management.Automation;
8+
using Microsoft.PowerShell.Commands;
9+
using Microsoft.PowerShell.PowerShellGet.UtilClasses;
10+
11+
namespace Microsoft.PowerShell.PowerShellGet.Cmdlets
12+
{
13+
/// <summary>
14+
/// Creates a new .ps1 file with script information required for publishing a script.
15+
/// </summary>
16+
[Cmdlet(VerbsCommon.New, "PSScriptFileInfo")]
17+
public sealed class NewPSScriptFileInfo : PSCmdlet
18+
{
19+
#region Parameters
20+
21+
/// <summary>
22+
/// The path the .ps1 script info file will be created at.
23+
/// </summary>
24+
[Parameter(Position = 0, Mandatory = true)]
25+
[ValidateNotNullOrEmpty]
26+
public string FilePath { get; set; }
27+
28+
/// <summary>
29+
/// The version of the script.
30+
/// </summary>
31+
[Parameter]
32+
[ValidateNotNullOrEmpty()]
33+
public string Version { get; set; }
34+
35+
/// <summary>
36+
/// The author of the script.
37+
/// </summary>
38+
[Parameter]
39+
[ValidateNotNullOrEmpty()]
40+
public string Author { get; set; }
41+
42+
/// <summary>
43+
/// The description of the script.
44+
/// </summary>
45+
[Parameter(Mandatory = true)]
46+
[ValidateNotNullOrEmpty()]
47+
public string Description { get; set; }
48+
49+
/// <summary>
50+
/// A unique identifier for the script. The GUID can be used to distinguish among scripts with the same name.
51+
/// </summary>
52+
[Parameter]
53+
[ValidateNotNullOrEmpty()]
54+
public Guid Guid { get; set; }
55+
56+
/// <summary>
57+
/// The name of the company owning the script.
58+
/// </summary>
59+
[Parameter]
60+
[ValidateNotNullOrEmpty()]
61+
public string CompanyName { get; set; }
62+
63+
/// <summary>
64+
/// The copyright statement for the script.
65+
/// </summary>
66+
[Parameter]
67+
[ValidateNotNullOrEmpty()]
68+
public string Copyright { get; set; }
69+
70+
/// <summary>
71+
/// The list of modules required by the script.
72+
/// </summary>
73+
[Parameter]
74+
[ValidateNotNullOrEmpty()]
75+
public Hashtable[] RequiredModules { get; set; }
76+
77+
/// <summary>
78+
/// The list of external module dependencies taken by this script.
79+
/// </summary>
80+
[Parameter]
81+
[ValidateNotNullOrEmpty()]
82+
public string[] ExternalModuleDependencies { get; set; }
83+
84+
/// <summary>
85+
/// The list of scripts required by the script.
86+
/// </summary>
87+
[Parameter]
88+
[ValidateNotNullOrEmpty()]
89+
public string[] RequiredScripts { get; set; }
90+
91+
/// <summary>
92+
/// The list of external script dependencies taken by this script.
93+
/// </summary>
94+
[Parameter]
95+
[ValidateNotNullOrEmpty()]
96+
public string[] ExternalScriptDependencies { get; set; }
97+
98+
/// <summary>
99+
/// The tags associated with the script.
100+
/// </summary>
101+
[Parameter]
102+
[ValidateNotNullOrEmpty()]
103+
public string[] Tags { get; set; }
104+
105+
/// <summary>
106+
/// The Uri for the project associated with the script.
107+
/// </summary>
108+
[Parameter]
109+
[ValidateNotNullOrEmpty()]
110+
public string ProjectUri { get; set; }
111+
112+
/// <summary>
113+
/// The Uri for the license associated with the script.
114+
/// </summary>
115+
[Parameter]
116+
[ValidateNotNullOrEmpty()]
117+
public string LicenseUri { get; set; }
118+
119+
/// <summary>
120+
/// The Uri for the icon associated with the script.
121+
/// </summary>
122+
[Parameter]
123+
[ValidateNotNullOrEmpty()]
124+
public string IconUri { get; set; }
125+
126+
/// <summary>
127+
/// The release notes for the script.
128+
/// </summary>
129+
[Parameter]
130+
[ValidateNotNullOrEmpty()]
131+
public string ReleaseNotes { get; set; }
132+
133+
/// <summary>
134+
/// The private data associated with the script.
135+
/// </summary>
136+
[Parameter]
137+
[ValidateNotNullOrEmpty()]
138+
public string PrivateData { get; set; }
139+
140+
/// <summary>
141+
/// If used with Path parameter and .ps1 file specified at the path exists, it rewrites the file.
142+
/// </summary>
143+
[Parameter]
144+
public SwitchParameter Force { get; set; }
145+
146+
#endregion
147+
148+
#region Methods
149+
150+
protected override void EndProcessing()
151+
{
152+
// validate Uri related parameters passed in as strings
153+
Uri projectUri = null;
154+
if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri,
155+
cmdletPassedIn: this,
156+
uriResult: out projectUri,
157+
errorRecord: out ErrorRecord projectErrorRecord))
158+
{
159+
ThrowTerminatingError(projectErrorRecord);
160+
}
161+
162+
Uri licenseUri = null;
163+
if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUri(uriString: LicenseUri,
164+
cmdletPassedIn: this,
165+
uriResult: out licenseUri,
166+
errorRecord: out ErrorRecord licenseErrorRecord))
167+
{
168+
ThrowTerminatingError(licenseErrorRecord);
169+
}
170+
171+
Uri iconUri = null;
172+
if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUri(uriString: IconUri,
173+
cmdletPassedIn: this,
174+
uriResult: out iconUri,
175+
errorRecord: out ErrorRecord iconErrorRecord))
176+
{
177+
ThrowTerminatingError(iconErrorRecord);
178+
}
179+
180+
if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase))
181+
{
182+
var exMessage = "Path needs to end with a .ps1 file. Example: C:/Users/john/x/MyScript.ps1";
183+
var ex = new ArgumentException(exMessage);
184+
var InvalidPathError = new ErrorRecord(ex, "InvalidPath", ErrorCategory.InvalidArgument, null);
185+
ThrowTerminatingError(InvalidPathError);
186+
}
187+
188+
var resolvedFilePath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(FilePath);
189+
if (String.IsNullOrEmpty(resolvedFilePath))
190+
{
191+
var exMessage = "Error: Could not resolve provided Path argument into a single path.";
192+
var ex = new PSArgumentException(exMessage);
193+
var InvalidPathArgumentError = new ErrorRecord(ex, "InvalidPathArgumentError", ErrorCategory.InvalidArgument, null);
194+
ThrowTerminatingError(InvalidPathArgumentError);
195+
}
196+
197+
if (File.Exists(resolvedFilePath) && !Force)
198+
{
199+
// .ps1 file at specified location already exists and Force parameter isn't used to rewrite the file
200+
var exMessage = ".ps1 file at specified path already exists. Specify a different location or use -Force parameter to overwrite the .ps1 file.";
201+
var ex = new ArgumentException(exMessage);
202+
var ScriptAtPathAlreadyExistsError = new ErrorRecord(ex, "ScriptAtPathAlreadyExists", ErrorCategory.InvalidArgument, null);
203+
ThrowTerminatingError(ScriptAtPathAlreadyExistsError);
204+
}
205+
206+
ModuleSpecification[] validatedRequiredModuleSpecifications = Array.Empty<ModuleSpecification>();
207+
if (RequiredModules != null && RequiredModules.Length > 0)
208+
{
209+
if (!Utils.TryCreateModuleSpecification(
210+
moduleSpecHashtables: RequiredModules,
211+
out validatedRequiredModuleSpecifications,
212+
out ErrorRecord[] moduleSpecErrors))
213+
{
214+
foreach (ErrorRecord err in moduleSpecErrors)
215+
{
216+
WriteError(err);
217+
}
218+
219+
return;
220+
}
221+
}
222+
223+
PSScriptFileInfo scriptInfo = new PSScriptFileInfo(
224+
version: Version,
225+
guid: Guid,
226+
author: Author,
227+
companyName: CompanyName,
228+
copyright: Copyright,
229+
tags: Tags,
230+
licenseUri: licenseUri,
231+
projectUri: projectUri,
232+
iconUri: iconUri,
233+
requiredModules: validatedRequiredModuleSpecifications,
234+
externalModuleDependencies: ExternalModuleDependencies,
235+
requiredScripts: RequiredScripts,
236+
externalScriptDependencies: ExternalScriptDependencies,
237+
releaseNotes: ReleaseNotes,
238+
privateData: PrivateData,
239+
description: Description);
240+
241+
if (!scriptInfo.TryCreateScriptFileInfoString(
242+
psScriptFileContents: out string[] psScriptFileContents,
243+
errors: out ErrorRecord[] errors))
244+
{
245+
foreach (ErrorRecord err in errors)
246+
{
247+
WriteError(err);
248+
}
249+
250+
return;
251+
}
252+
253+
File.WriteAllLines(resolvedFilePath, psScriptFileContents);
254+
}
255+
256+
#endregion
257+
}
258+
}

src/code/PSScriptContents.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
6+
namespace Microsoft.PowerShell.PowerShellGet.UtilClasses
7+
{
8+
/// <summary>
9+
/// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents).
10+
/// </summary>
11+
public sealed class PSScriptContents
12+
{
13+
#region Properties
14+
15+
/// <summary>
16+
/// End of file contents for the .ps1 file.
17+
/// </summary>
18+
public string[] EndOfFileContents { get; private set; } = Utils.EmptyStrArray;
19+
20+
/// <summary>
21+
/// End of file contents for the .ps1 file.
22+
/// </summary>
23+
public bool ContainsSignature { get; set; } = false;
24+
25+
#endregion
26+
27+
#region Private Members
28+
29+
private const string signatureStartString = "# SIG # Begin signature block";
30+
private int _signatureStartIndex = -1;
31+
32+
#endregion
33+
34+
#region Constructor
35+
36+
/// <summary>
37+
/// This constructor takes end of file contents as a string and checks if it has a signature.
38+
/// </summary>
39+
public PSScriptContents(string[] endOfFileContents)
40+
{
41+
EndOfFileContents = endOfFileContents;
42+
ContainsSignature = CheckForSignature();
43+
}
44+
45+
/// <summary>
46+
/// This constructor creates a PSScriptContents instance with default values for its properties.
47+
/// The calling method, like PSScriptContents.ParseContent() could then populate the properties.
48+
/// </summary>
49+
internal PSScriptContents() {}
50+
51+
#endregion
52+
53+
#region Internal Methods
54+
55+
/// <summary>
56+
/// Parses end of file contents as a string from the file lines passed in
57+
/// and sets property indicating whether those contents contain a signature.
58+
/// </summary>
59+
internal void ParseContent(string[] commentLines)
60+
{
61+
if (commentLines.Length != 0)
62+
{
63+
EndOfFileContents = commentLines;
64+
ContainsSignature = CheckForSignature();
65+
}
66+
}
67+
68+
/// <summary>
69+
/// This function is called by PSScriptFileInfo.TryCreateScriptFileInfoString(),
70+
/// by the New-PSScriptFileInfo cmdlet (in which case EndOfFileContents is an empty string so there's no signature that'll get removed)
71+
/// or by Update-PSScriptFileInfo cmdlet (in which case EndOfFileContents may not be empty and may contain a signature.
72+
/// When emitting contents, any file signature is always removed because it is invalidated when the content is updated.
73+
/// </summary>
74+
internal string[] EmitContent()
75+
{
76+
RemoveSignatureString();
77+
return EndOfFileContents;
78+
}
79+
80+
#endregion
81+
82+
#region Private Methods
83+
84+
/// <summary>
85+
/// Checks if the end of file contents contain a signature.
86+
/// </summary>
87+
private bool CheckForSignature()
88+
{
89+
for (int i = 0; i < EndOfFileContents.Length; i++)
90+
{
91+
if (String.Equals(EndOfFileContents[i], signatureStartString, StringComparison.InvariantCultureIgnoreCase))
92+
{
93+
_signatureStartIndex = i;
94+
}
95+
}
96+
97+
return _signatureStartIndex != -1;
98+
}
99+
100+
/// <summary>
101+
/// Removes the signature from EndOfFileContents property
102+
/// as the signature would be invalidated during update.
103+
/// </summary>
104+
private void RemoveSignatureString()
105+
{
106+
if (ContainsSignature)
107+
{
108+
string[] newEndOfFileContents = new string[EndOfFileContents.Length - _signatureStartIndex];
109+
Array.Copy(EndOfFileContents, newEndOfFileContents, _signatureStartIndex);
110+
EndOfFileContents = newEndOfFileContents;
111+
112+
ContainsSignature = false;
113+
}
114+
}
115+
#endregion
116+
}
117+
}

0 commit comments

Comments
 (0)