diff --git a/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs b/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs index 8e1afd96929..b0e1267973e 100644 --- a/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs +++ b/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs @@ -495,6 +495,26 @@ public object PrivateData [ValidateNotNullOrEmpty] public string ReleaseNotes { get; set; } + /// + /// Gets or sets whether or not the module is a prerelease. + /// + [Parameter] + [ValidateNotNullOrEmpty] + public string Prerelease { get; set; } + + /// + /// Gets or sets whether or not the module requires explicit user acceptance for install/update/save. + /// + [Parameter] + public SwitchParameter RequireLicenseAcceptance { get; set; } = true; + + /// + /// Gets or sets the external module dependencies. + /// + [Parameter] + [ValidateNotNullOrEmpty] + public string[] ExternalModuleDependencies { get; set; } + /// /// Specify the HelpInfo URI. /// @@ -1044,65 +1064,65 @@ protected override void EndProcessing() if (_rootModule == null) _rootModule = string.Empty; - BuildModuleManifest(result, "RootModule", Modules.RootModule, !string.IsNullOrEmpty(_rootModule), () => QuoteName(_rootModule), streamWriter); + BuildModuleManifest(result, nameof(RootModule), Modules.RootModule, !string.IsNullOrEmpty(_rootModule), () => QuoteName(_rootModule), streamWriter); - BuildModuleManifest(result, "ModuleVersion", Modules.ModuleVersion, _moduleVersion != null && !string.IsNullOrEmpty(_moduleVersion.ToString()), () => QuoteName(_moduleVersion), streamWriter); + BuildModuleManifest(result, nameof(ModuleVersion), Modules.ModuleVersion, _moduleVersion != null && !string.IsNullOrEmpty(_moduleVersion.ToString()), () => QuoteName(_moduleVersion), streamWriter); - BuildModuleManifest(result, "CompatiblePSEditions", Modules.CompatiblePSEditions, _compatiblePSEditions != null && _compatiblePSEditions.Length > 0, () => QuoteNames(_compatiblePSEditions, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(CompatiblePSEditions), Modules.CompatiblePSEditions, _compatiblePSEditions != null && _compatiblePSEditions.Length > 0, () => QuoteNames(_compatiblePSEditions, streamWriter), streamWriter); - BuildModuleManifest(result, "GUID", Modules.GUID, !string.IsNullOrEmpty(_guid.ToString()), () => QuoteName(_guid.ToString()), streamWriter); + BuildModuleManifest(result, nameof(Modules.GUID), Modules.GUID, !string.IsNullOrEmpty(_guid.ToString()), () => QuoteName(_guid.ToString()), streamWriter); - BuildModuleManifest(result, "Author", Modules.Author, !string.IsNullOrEmpty(_author), () => QuoteName(Author), streamWriter); + BuildModuleManifest(result, nameof(Author), Modules.Author, !string.IsNullOrEmpty(_author), () => QuoteName(Author), streamWriter); - BuildModuleManifest(result, "CompanyName", Modules.CompanyName, !string.IsNullOrEmpty(_companyName), () => QuoteName(_companyName), streamWriter); + BuildModuleManifest(result, nameof(CompanyName), Modules.CompanyName, !string.IsNullOrEmpty(_companyName), () => QuoteName(_companyName), streamWriter); - BuildModuleManifest(result, "Copyright", Modules.Copyright, !string.IsNullOrEmpty(_copyright), () => QuoteName(_copyright), streamWriter); + BuildModuleManifest(result, nameof(Copyright), Modules.Copyright, !string.IsNullOrEmpty(_copyright), () => QuoteName(_copyright), streamWriter); - BuildModuleManifest(result, "Description", Modules.Description, !string.IsNullOrEmpty(_description), () => QuoteName(_description), streamWriter); + BuildModuleManifest(result, nameof(Description), Modules.Description, !string.IsNullOrEmpty(_description), () => QuoteName(_description), streamWriter); - BuildModuleManifest(result, "PowerShellVersion", Modules.PowerShellVersion, _powerShellVersion != null && !string.IsNullOrEmpty(_powerShellVersion.ToString()), () => QuoteName(_powerShellVersion), streamWriter); + BuildModuleManifest(result, nameof(PowerShellVersion), Modules.PowerShellVersion, _powerShellVersion != null && !string.IsNullOrEmpty(_powerShellVersion.ToString()), () => QuoteName(_powerShellVersion), streamWriter); - BuildModuleManifest(result, "PowerShellHostName", Modules.PowerShellHostName, !string.IsNullOrEmpty(_PowerShellHostName), () => QuoteName(_PowerShellHostName), streamWriter); + BuildModuleManifest(result, nameof(PowerShellHostName), Modules.PowerShellHostName, !string.IsNullOrEmpty(_PowerShellHostName), () => QuoteName(_PowerShellHostName), streamWriter); - BuildModuleManifest(result, "PowerShellHostVersion", Modules.PowerShellHostVersion, _PowerShellHostVersion != null && !string.IsNullOrEmpty(_PowerShellHostVersion.ToString()), () => QuoteName(_PowerShellHostVersion), streamWriter); + BuildModuleManifest(result, nameof(PowerShellHostVersion), Modules.PowerShellHostVersion, _PowerShellHostVersion != null && !string.IsNullOrEmpty(_PowerShellHostVersion.ToString()), () => QuoteName(_PowerShellHostVersion), streamWriter); - BuildModuleManifest(result, "DotNetFrameworkVersion", StringUtil.Format(Modules.DotNetFrameworkVersion, Modules.PrerequisiteForDesktopEditionOnly), _DotNetFrameworkVersion != null && !string.IsNullOrEmpty(_DotNetFrameworkVersion.ToString()), () => QuoteName(_DotNetFrameworkVersion), streamWriter); + BuildModuleManifest(result, nameof(DotNetFrameworkVersion), StringUtil.Format(Modules.DotNetFrameworkVersion, Modules.PrerequisiteForDesktopEditionOnly), _DotNetFrameworkVersion != null && !string.IsNullOrEmpty(_DotNetFrameworkVersion.ToString()), () => QuoteName(_DotNetFrameworkVersion), streamWriter); - BuildModuleManifest(result, "CLRVersion", StringUtil.Format(Modules.CLRVersion, Modules.PrerequisiteForDesktopEditionOnly), _ClrVersion != null && !string.IsNullOrEmpty(_ClrVersion.ToString()), () => QuoteName(_ClrVersion), streamWriter); + BuildModuleManifest(result, nameof(Modules.CLRVersion), StringUtil.Format(Modules.CLRVersion, Modules.PrerequisiteForDesktopEditionOnly), _ClrVersion != null && !string.IsNullOrEmpty(_ClrVersion.ToString()), () => QuoteName(_ClrVersion), streamWriter); - BuildModuleManifest(result, "ProcessorArchitecture", Modules.ProcessorArchitecture, _processorArchitecture.HasValue, () => QuoteName(_processorArchitecture.ToString()), streamWriter); + BuildModuleManifest(result, nameof(ProcessorArchitecture), Modules.ProcessorArchitecture, _processorArchitecture.HasValue, () => QuoteName(_processorArchitecture.ToString()), streamWriter); - BuildModuleManifest(result, "RequiredModules", Modules.RequiredModules, _requiredModules != null && _requiredModules.Length > 0, () => QuoteModules(_requiredModules, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(RequiredModules), Modules.RequiredModules, _requiredModules != null && _requiredModules.Length > 0, () => QuoteModules(_requiredModules, streamWriter), streamWriter); - BuildModuleManifest(result, "RequiredAssemblies", Modules.RequiredAssemblies, _requiredAssemblies != null, () => QuoteFiles(_requiredAssemblies, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(RequiredAssemblies), Modules.RequiredAssemblies, _requiredAssemblies != null, () => QuoteFiles(_requiredAssemblies, streamWriter), streamWriter); - BuildModuleManifest(result, "ScriptsToProcess", Modules.ScriptsToProcess, _scripts != null, () => QuoteFiles(_scripts, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(ScriptsToProcess), Modules.ScriptsToProcess, _scripts != null, () => QuoteFiles(_scripts, streamWriter), streamWriter); - BuildModuleManifest(result, "TypesToProcess", Modules.TypesToProcess, _types != null, () => QuoteFiles(_types, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(TypesToProcess), Modules.TypesToProcess, _types != null, () => QuoteFiles(_types, streamWriter), streamWriter); - BuildModuleManifest(result, "FormatsToProcess", Modules.FormatsToProcess, _formats != null, () => QuoteFiles(_formats, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(FormatsToProcess), Modules.FormatsToProcess, _formats != null, () => QuoteFiles(_formats, streamWriter), streamWriter); - BuildModuleManifest(result, "NestedModules", Modules.NestedModules, _nestedModules != null, () => QuoteModules(PreProcessModuleSpec(_nestedModules), streamWriter), streamWriter); + BuildModuleManifest(result, nameof(NestedModules), Modules.NestedModules, _nestedModules != null, () => QuoteModules(PreProcessModuleSpec(_nestedModules), streamWriter), streamWriter); - BuildModuleManifest(result, "FunctionsToExport", Modules.FunctionsToExport, true, () => QuoteNames(_exportedFunctions, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(FunctionsToExport), Modules.FunctionsToExport, true, () => QuoteNames(_exportedFunctions, streamWriter), streamWriter); - BuildModuleManifest(result, "CmdletsToExport", Modules.CmdletsToExport, true, () => QuoteNames(_exportedCmdlets, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(CmdletsToExport), Modules.CmdletsToExport, true, () => QuoteNames(_exportedCmdlets, streamWriter), streamWriter); - BuildModuleManifest(result, "VariablesToExport", Modules.VariablesToExport, _exportedVariables != null && _exportedVariables.Length > 0, () => QuoteNames(_exportedVariables, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(VariablesToExport), Modules.VariablesToExport, _exportedVariables != null && _exportedVariables.Length > 0, () => QuoteNames(_exportedVariables, streamWriter), streamWriter); - BuildModuleManifest(result, "AliasesToExport", Modules.AliasesToExport, true, () => QuoteNames(_exportedAliases, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(AliasesToExport), Modules.AliasesToExport, true, () => QuoteNames(_exportedAliases, streamWriter), streamWriter); - BuildModuleManifest(result, "DscResourcesToExport", Modules.DscResourcesToExport, _dscResourcesToExport != null && _dscResourcesToExport.Length > 0, () => QuoteNames(_dscResourcesToExport, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(DscResourcesToExport), Modules.DscResourcesToExport, _dscResourcesToExport != null && _dscResourcesToExport.Length > 0, () => QuoteNames(_dscResourcesToExport, streamWriter), streamWriter); - BuildModuleManifest(result, "ModuleList", Modules.ModuleList, _moduleList != null, () => QuoteModules(_moduleList, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(ModuleList), Modules.ModuleList, _moduleList != null, () => QuoteModules(_moduleList, streamWriter), streamWriter); - BuildModuleManifest(result, "FileList", Modules.FileList, _miscFiles != null, () => QuoteFiles(_miscFiles, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(FileList), Modules.FileList, _miscFiles != null, () => QuoteFiles(_miscFiles, streamWriter), streamWriter); BuildPrivateDataInModuleManifest(result, streamWriter); - BuildModuleManifest(result, "HelpInfoURI", Modules.HelpInfoURI, !string.IsNullOrEmpty(_helpInfoUri), () => QuoteName((_helpInfoUri != null) ? new Uri(_helpInfoUri) : null), streamWriter); + BuildModuleManifest(result, nameof(Modules.HelpInfoURI), Modules.HelpInfoURI, !string.IsNullOrEmpty(_helpInfoUri), () => QuoteName((_helpInfoUri != null) ? new Uri(_helpInfoUri) : null), streamWriter); - BuildModuleManifest(result, "DefaultCommandPrefix", Modules.DefaultCommandPrefix, !string.IsNullOrEmpty(_defaultCommandPrefix), () => QuoteName(_defaultCommandPrefix), streamWriter); + BuildModuleManifest(result, nameof(DefaultCommandPrefix), Modules.DefaultCommandPrefix, !string.IsNullOrEmpty(_defaultCommandPrefix), () => QuoteName(_defaultCommandPrefix), streamWriter); result.Append("}"); result.Append(streamWriter.NewLine); @@ -1174,7 +1194,7 @@ private void BuildPrivateDataInModuleManifest(StringBuilder result, StreamWriter { WriteWarning(Modules.PrivateDataValueTypeShouldBeHashTableWarning); - BuildModuleManifest(result, "PrivateData", Modules.PrivateData, _privateData != null, + BuildModuleManifest(result, nameof(PrivateData), Modules.PrivateData, _privateData != null, () => QuoteName((string)LanguagePrimitives.ConvertTo(_privateData, typeof(string), CultureInfo.InvariantCulture)), streamWriter); } @@ -1192,11 +1212,14 @@ private void BuildPrivateDataInModuleManifest(StringBuilder result, StreamWriter _indent = " "; - BuildModuleManifest(result, "Tags", Modules.Tags, Tags != null && Tags.Length > 0, () => QuoteNames(Tags, streamWriter), streamWriter); - BuildModuleManifest(result, "LicenseUri", Modules.LicenseUri, LicenseUri != null, () => QuoteName(LicenseUri), streamWriter); - BuildModuleManifest(result, "ProjectUri", Modules.ProjectUri, ProjectUri != null, () => QuoteName(ProjectUri), streamWriter); - BuildModuleManifest(result, "IconUri", Modules.IconUri, IconUri != null, () => QuoteName(IconUri), streamWriter); - BuildModuleManifest(result, "ReleaseNotes", Modules.ReleaseNotes, !string.IsNullOrEmpty(ReleaseNotes), () => QuoteName(ReleaseNotes), streamWriter); + BuildModuleManifest(result, nameof(Tags), Modules.Tags, Tags != null && Tags.Length > 0, () => QuoteNames(Tags, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(LicenseUri), Modules.LicenseUri, LicenseUri != null, () => QuoteName(LicenseUri), streamWriter); + BuildModuleManifest(result, nameof(ProjectUri), Modules.ProjectUri, ProjectUri != null, () => QuoteName(ProjectUri), streamWriter); + BuildModuleManifest(result, nameof(IconUri), Modules.IconUri, IconUri != null, () => QuoteName(IconUri), streamWriter); + BuildModuleManifest(result, nameof(ReleaseNotes), Modules.ReleaseNotes, !string.IsNullOrEmpty(ReleaseNotes), () => QuoteName(ReleaseNotes), streamWriter); + BuildModuleManifest(result, nameof(Prerelease), Modules.Prerelease, !string.IsNullOrEmpty(Prerelease), () => QuoteName(Prerelease), streamWriter); + BuildModuleManifest(result, nameof(RequireLicenseAcceptance), Modules.RequireLicenseAcceptance, RequireLicenseAcceptance.IsPresent, () => { return RequireLicenseAcceptance.IsPresent ? "$true" : "$false"; }, streamWriter); + BuildModuleManifest(result, nameof(ExternalModuleDependencies), Modules.ExternalModuleDependencies, ExternalModuleDependencies != null && ExternalModuleDependencies.Length > 0, () => QuoteNames(ExternalModuleDependencies, streamWriter), streamWriter); result.Append(" } "); result.Append(ManifestComment(StringUtil.Format(Modules.EndOfManifestHashTable, "PSData"), streamWriter)); diff --git a/src/System.Management.Automation/resources/Modules.resx b/src/System.Management.Automation/resources/Modules.resx index 90f0db5c224..ee0f04b15ce 100644 --- a/src/System.Management.Automation/resources/Modules.resx +++ b/src/System.Management.Automation/resources/Modules.resx @@ -426,6 +426,15 @@ ReleaseNotes of this module + + Prerelease string of this module + + + Flag to indicate whether the module requires explicit user acceptance for install/update/save + + + External dependent modules of this module + End of {0} hashtable diff --git a/test/powershell/engine/Module/NewModuleManifest.Tests.ps1 b/test/powershell/engine/Module/NewModuleManifest.Tests.ps1 index 945b90f62a1..faacc9f3551 100644 --- a/test/powershell/engine/Module/NewModuleManifest.Tests.ps1 +++ b/test/powershell/engine/Module/NewModuleManifest.Tests.ps1 @@ -1,16 +1,75 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -Describe "New-ModuleManifest tests" -tags "CI" { - BeforeEach { - $null = New-Item -ItemType Directory -Path testdrive:/module - $testModulePath = "testdrive:/module/test.psd1" + +Describe "New-ModuleManifest basic tests" -tags "CI" { + BeforeAll { + $moduleName = 'test' + $modulePath = "$TestDrive/Modules/$moduleName" + $manifestPath = Join-Path $modulePath "$moduleName.psd1" + + New-Item -Path "$TestDrive/Modules/$moduleName" -ItemType Directory } AfterEach { - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue testdrive:/module + Remove-Item -Path $manifestPath -Force -ErrorAction SilentlyContinue + } + + AfterAll { + Remove-Item -Path $modulePath -Recurse -Force -ErrorAction SilentlyContinue } + It "Verify manifest fields 1" { + New-ModuleManifest -Path $manifestPath + $module = Test-ModuleManifest -Path $manifestPath + $module.Name | Should -BeExactly "test" + $module.ModuleType | Should -BeExactly "Manifest" + $module.Version | Should -BeExactly "0.0.1" + } + + It "Verify manifest fields 2" { + New-ModuleManifest -Path $manifestPath ` + -Author 'author' ` + -CompanyName 'company' ` + -Copyright 'copyright' ` + -ModuleVersion '1.2.3' ` + -Description 'description' ` + -PowerShellVersion '6.0' ` + -ClrVersion '1.2.3' ` + -DotNetFrameworkVersion '3.2.1' ` + -PowerShellHostVersion '1.2.3' ` + -Tags @('tag1', 'tag2') ` + -ReleaseNotes 'release note' ` + -RequiredModules @('PSReadline') ` + -ExternalModuleDependencies @('PSReadline') ` + -Prerelease 'prerelease' ` + -RequireLicenseAcceptance + + $module = Test-ModuleManifest -Path $manifestPath + $module.Name | Should -BeExactly "test" + $module.Author | Should -BeExactly "author" + $module.Version | Should -BeExactly "1.2.3" + $module.Description | Should -BeExactly "description" + $module.PowerShellVersion | Should -BeExactly "6.0" + $module.ClrVersion | Should -BeExactly "1.2.3" + $module.DotNetFrameworkVersion | Should -BeExactly "3.2.1" + $module.PowerShellHostVersion | Should -BeExactly "1.2.3" + $module.Tags | Should -BeExactly @('tag1', 'tag2') + $module.ReleaseNotes | Should -BeExactly 'release note' + $module.RequiredModules | Should -BeExactly 'PSReadline' + $module.PrivateData.PSData.ExternalModuleDependencies | Should -BeExactly 'PSReadline' + $module.PrivateData.PSData.Prerelease | Should -BeExactly 'prerelease' + $module.PrivateData.PSData.RequireLicenseAcceptance | Should -BeExactly $true + } +} + +Describe "New-ModuleManifest tests" -tags "CI" { BeforeAll { + $moduleName = 'test' + $modulePath = "$TestDrive/Modules/$moduleName" + $manifestPath = Join-Path $modulePath "$moduleName.psd1" + + New-Item -Path "$TestDrive/Modules/$moduleName" -ItemType Directory + if ($IsWindows) { $ExpectedManifestBytes = @(35,13) # CR } else { @@ -18,12 +77,21 @@ Describe "New-ModuleManifest tests" -tags "CI" { } } + AfterEach { + Remove-Item -Path $manifestPath -Force -ErrorAction SilentlyContinue + } + + AfterAll { + Remove-Item -Path $modulePath -Recurse -Force -ErrorAction SilentlyContinue + } + + It "Uris with spaces are allowed and escaped correctly" { $testUri = [Uri]"http://foo.com/hello world" $absoluteUri = $testUri.AbsoluteUri - New-ModuleManifest -Path $testModulePath -ProjectUri $testUri -LicenseUri $testUri -IconUri $testUri -HelpInfoUri $testUri - $module = Test-ModuleManifest -Path $testModulePath + New-ModuleManifest -Path $manifestPath -ProjectUri $testUri -LicenseUri $testUri -IconUri $testUri -HelpInfoUri $testUri + $module = Test-ModuleManifest -Path $manifestPath $module.HelpInfoUri | Should -BeExactly $absoluteUri $module.PrivateData.PSData.IconUri | Should -BeExactly $absoluteUri $module.PrivateData.PSData.LicenseUri | Should -BeExactly $absoluteUri @@ -32,8 +100,8 @@ Describe "New-ModuleManifest tests" -tags "CI" { function TestNewModuleManifestEncoding { param ([byte[]]$expected) - New-ModuleManifest -Path $testModulePath - (Get-Content -AsByteStream -Path $testModulePath -TotalCount $expected.Length) -join ',' | Should -Be ($expected -join ',') + New-ModuleManifest -Path $manifestPath + (Get-Content -AsByteStream -Path $manifestPath -TotalCount $expected.Length) -join ',' | Should -Be ($expected -join ',') } It "Verify module manifest encoding" { @@ -46,6 +114,6 @@ Describe "New-ModuleManifest tests" -tags "CI" { It "Relative URIs are not allowed" { $testUri = [Uri]"../foo" - { New-ModuleManifest -Path $testModulePath -ProjectUri $testUri -LicenseUri $testUri -IconUri $testUri } | Should -Throw -ErrorId "System.InvalidOperationException,Microsoft.PowerShell.Commands.NewModuleManifestCommand" + { New-ModuleManifest -Path $manifestPath -ProjectUri $testUri -LicenseUri $testUri -IconUri $testUri } | Should -Throw -ErrorId "System.InvalidOperationException,Microsoft.PowerShell.Commands.NewModuleManifestCommand" } }