-
-
Save tyconsulting/c567e5cc66fc522d46743d744579be27 to your computer and use it in GitHub Desktop.
Generate NuGet specification .nuspec file based on PowerShell module manifest .psd1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Requires -Modules PowerShellGet | |
#Requires -Version 5.0 | |
<# | |
======================================================================================================= | |
AUTHOR: Tao Yang | |
DATE: 05/09/2018 | |
Version: 1.0 | |
Comment: generate nuget specification file (.nuspec) based on PowerShell module manifest (.psd1) file | |
======================================================================================================= | |
#> | |
[CmdletBinding(PositionalBinding = $false)] | |
Param | |
( | |
[Parameter(Mandatory = $true)] | |
[ValidateScript({ | |
Test-Path $_ -PathType leaf -Include '*.psd1' | |
})] | |
[string] | |
$ManifestPath, | |
[Parameter(Mandatory = $false)] | |
[ValidateScript({ | |
Test-Path $_ -PathType 'Container' | |
})] | |
[string] | |
$DestinationFolder | |
) | |
#region functions | |
function Get-EscapedString | |
{ | |
[CmdletBinding()] | |
[OutputType([String])] | |
Param | |
( | |
[Parameter()] | |
[string] | |
$ElementValue | |
) | |
return [System.Security.SecurityElement]::Escape($ElementValue) | |
} | |
function Get-ExportedDscResources | |
{ | |
[CmdletBinding(PositionalBinding = $false)] | |
Param | |
( | |
[Parameter(Mandatory = $true)] | |
[ValidateNotNullOrEmpty()] | |
[PSModuleInfo] | |
$PSModuleInfo | |
) | |
$dscResources = @() | |
if(Get-Command -Name Get-DscResource -Module PSDesiredStateConfiguration -ErrorAction Ignore) | |
{ | |
$OldPSModulePath = $env:PSModulePath | |
try | |
{ | |
$env:PSModulePath = Join-Path -Path $PSHOME -ChildPath 'Modules' | |
$env:PSModulePath = "$env:PSModulePath;$(Split-Path -Path $PSModuleInfo.ModuleBase -Parent)" | |
$dscResources = PSDesiredStateConfiguration\Get-DscResource -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | | |
Microsoft.PowerShell.Core\ForEach-Object { | |
if($_.Module -and ($_.Module.Name -eq $PSModuleInfo.Name)) | |
{ | |
$_.Name | |
} | |
} | |
} | |
finally | |
{ | |
$env:PSModulePath = $OldPSModulePath | |
} | |
} | |
else | |
{ | |
$dscResourcesDir = Join-PathUtility -Path $PSModuleInfo.ModuleBase -ChildPath 'DscResources' -PathType Directory | |
if(Microsoft.PowerShell.Management\Test-Path $dscResourcesDir) | |
{ | |
$dscResources = Microsoft.PowerShell.Management\Get-ChildItem -Path $dscResourcesDir -Directory -Name | |
} | |
} | |
return $dscResources | |
} | |
function Get-AvailableRoleCapabilityName | |
{ | |
[CmdletBinding(PositionalBinding = $false)] | |
Param | |
( | |
[Parameter(Mandatory = $true)] | |
[ValidateNotNullOrEmpty()] | |
[PSModuleInfo] | |
$PSModuleInfo | |
) | |
$RoleCapabilityNames = @() | |
$RoleCapabilitiesDir = Join-PathUtility -Path $PSModuleInfo.ModuleBase -ChildPath 'RoleCapabilities' -PathType Directory | |
if(Microsoft.PowerShell.Management\Test-Path -Path $RoleCapabilitiesDir -PathType Container) | |
{ | |
$RoleCapabilityNames = Microsoft.PowerShell.Management\Get-ChildItem -Path $RoleCapabilitiesDir ` | |
-Name -Filter *.psrc | | |
ForEach-Object -Process { | |
[System.IO.Path]::GetFileNameWithoutExtension($_) | |
} | |
} | |
return $RoleCapabilityNames | |
} | |
function Join-PathUtility | |
{ | |
<# | |
.DESCRIPTION | |
Utility to get the case-sensitive path, if exists. | |
Otherwise, returns the output of Join-Path cmdlet. | |
This is required for getting the case-sensitive paths on non-Windows platforms. | |
#> | |
param | |
( | |
[Parameter(Mandatory = $true)] | |
[string] | |
$Path, | |
[Parameter(Mandatory = $false)] | |
[string] | |
$ChildPath, | |
[Parameter(Mandatory = $false)] | |
[string] | |
[ValidateSet('File', 'Directory', 'Any')] | |
$PathType = 'Any' | |
) | |
$JoinedPath = Microsoft.PowerShell.Management\Join-Path -Path $Path -ChildPath $ChildPath | |
if(Microsoft.PowerShell.Management\Test-Path -Path $Path -PathType Container) | |
{ | |
$GetChildItem_params = @{ | |
Path = $Path | |
ErrorAction = 'SilentlyContinue' | |
WarningAction = 'SilentlyContinue' | |
} | |
if($PathType -eq 'File') | |
{ | |
$GetChildItem_params['File'] = $true | |
} | |
elseif($PathType -eq 'Directory') | |
{ | |
$GetChildItem_params['Directory'] = $true | |
} | |
$FoundPath = Microsoft.PowerShell.Management\Get-ChildItem @GetChildItem_params | | |
Where-Object -FilterScript { | |
$_.Name -eq $ChildPath | |
} | | |
ForEach-Object -Process { | |
$_.FullName | |
} | | |
Select-Object -First 1 -ErrorAction SilentlyContinue | |
if($FoundPath) | |
{ | |
$JoinedPath = $FoundPath | |
} | |
} | |
return $JoinedPath | |
} | |
function Get-ManifestHashTable | |
{ | |
param | |
( | |
[Parameter(Mandatory = $true)] | |
[ValidateNotNullOrEmpty()] | |
[string] | |
$Path | |
) | |
$Lines = $null | |
try | |
{ | |
$Lines = Get-Content -Path $Path -Force | |
} | |
catch | |
{ | |
Throw "Unable to parse manifest file '$path'" | |
Exit -1 | |
} | |
if(-not $Lines) | |
{ | |
return | |
} | |
$scriptBlock = [ScriptBlock]::Create( $Lines -join "`n" ) | |
$allowedVariables = [System.Collections.Generic.List[String]] @('PSEdition', 'PSScriptRoot') | |
$allowedCommands = [System.Collections.Generic.List[String]] @() | |
$allowEnvironmentVariables = $false | |
try | |
{ | |
$scriptBlock.CheckRestrictedLanguage($allowedCommands, $allowedVariables, $allowEnvironmentVariables) | |
} | |
catch | |
{ | |
return | |
} | |
return $scriptBlock.InvokeReturnAsIs() | |
} | |
function New-NuSpecFile | |
{ | |
[CmdletBinding(PositionalBinding = $false)] | |
Param | |
( | |
[Parameter(Mandatory = $true, ParameterSetName = 'PublishModule')] | |
[ValidateNotNullOrEmpty()] | |
[string] | |
$ManifestPath, | |
[Parameter(Mandatory = $false)] | |
[string] | |
$DestinationFolder | |
) | |
#variables | |
$Name = $null | |
$Description = $null | |
$Version = '' | |
$Author = $null | |
$CompanyName = $null | |
$Copyright = $null | |
$requireLicenseAcceptance = 'false' | |
$PSModuleInfo = Test-ModuleManifest -Path $ManifestPath | |
If (-not $PSModuleInfo) | |
{ | |
Throw "Failed to retrieve module information from manifest '$ManifestPath'" | |
exit -1 | |
} | |
$Name = $PSModuleInfo.Name | |
$Description = $PSModuleInfo.Description | |
$Version = $PSModuleInfo.Version | |
$Author = $PSModuleInfo.Author | |
$CompanyName = $PSModuleInfo.CompanyName | |
$Copyright = $PSModuleInfo.Copyright | |
If (-not $PSBoundParameters.ContainsKey('DestinationFolder')) | |
{ | |
$DestinationFolder = $PSModuleInfo.ModuleBase | |
} | |
$NuspecPath = Microsoft.PowerShell.Management\Join-Path -Path $DestinationFolder -ChildPath "$($PSModuleInfo.Name).nuspec" | |
if($PSModuleInfo.PrivateData -and | |
($PSModuleInfo.PrivateData.GetType().ToString() -eq 'System.Collections.Hashtable') -and | |
$PSModuleInfo.PrivateData['PSData'] -and | |
($PSModuleInfo.PrivateData['PSData'].GetType().ToString() -eq 'System.Collections.Hashtable') | |
) | |
{ | |
if($PSModuleInfo.PrivateData.PSData['Tags']) | |
{ | |
$Tags = $PSModuleInfo.PrivateData.PSData.Tags | |
} | |
if($PSModuleInfo.PrivateData.PSData['ReleaseNotes']) | |
{ | |
$ReleaseNotes = $PSModuleInfo.PrivateData.PSData.ReleaseNotes | |
} | |
if($PSModuleInfo.PrivateData.PSData['LicenseUri']) | |
{ | |
$LicenseUri = $PSModuleInfo.PrivateData.PSData.LicenseUri | |
} | |
if($PSModuleInfo.PrivateData.PSData['IconUri']) | |
{ | |
$IconUri = $PSModuleInfo.PrivateData.PSData.IconUri | |
} | |
if($PSModuleInfo.PrivateData.PSData['ProjectUri']) | |
{ | |
$ProjectUri = $PSModuleInfo.PrivateData.PSData.ProjectUri | |
} | |
if ($PSModuleInfo.PrivateData.PSData['Prerelease']) | |
{ | |
$psmoduleInfoPrereleaseString = $PSModuleInfo.PrivateData.PSData.Prerelease | |
if ($psmoduleInfoPrereleaseString -and $psmoduleInfoPrereleaseString.StartsWith('-')) | |
{ | |
$Version = [string]$Version + $psmoduleInfoPrereleaseString | |
} | |
else | |
{ | |
$Version = [string]$Version + '-' + $psmoduleInfoPrereleaseString | |
} | |
} | |
if($PSModuleInfo.PrivateData.PSData['RequireLicenseAcceptance']) | |
{ | |
$requireLicenseAcceptance = $PSModuleInfo.PrivateData.PSData.requireLicenseAcceptance.ToString().ToLower() | |
if($requireLicenseAcceptance -eq 'true') | |
{ | |
if(-not $LicenseUri) | |
{ | |
$message = "'LicenseUri' is not specified. 'LicenseUri' must be provided when user license acceptance is required." | |
Throw $message | |
} | |
$LicenseFilePath = Join-PathUtility -Path $PSModuleInfo.ModuleBase -ChildPath 'License.txt' -PathType File | |
if(-not $LicenseFilePath -or -not (Test-Path -Path $LicenseFilePath -PathType Leaf)) | |
{ | |
$message = 'License.txt not Found. License.txt must be provided when user license acceptance is required.' | |
Throw $message | |
} | |
if((Get-Content -LiteralPath $LicenseFilePath) -eq $null) | |
{ | |
$message = 'License.txt is empty.' | |
Throw $message | |
} | |
} | |
elseif($requireLicenseAcceptance -ne 'false') | |
{ | |
$InvalidValueForRequireLicenseAcceptance = "The specified value '{0}' for the parameter '{1}' is invalid. It should be $true or $false." -f ($requireLicenseAcceptance, 'requireLicenseAcceptance') | |
Write-Warning -Message $InvalidValueForRequireLicenseAcceptance | |
} | |
} | |
} | |
# Add PSModule and PSGet format version tags | |
if(-not $Tags) | |
{ | |
$Tags = @() | |
} | |
$DependentModuleDetails = @() | |
$Tags += 'PSModule' | |
$ModuleManifestHashTable = Get-ManifestHashTable -Path $ManifestPath | |
if($PSModuleInfo.ExportedCommands.Count) | |
{ | |
if($PSModuleInfo.ExportedCmdlets.Count) | |
{ | |
$Tags += 'PSIncludes_Cmdlet' | |
$Tags += $PSModuleInfo.ExportedCmdlets.Keys | Microsoft.PowerShell.Core\ForEach-Object { | |
"PSCmdlet_$_" | |
} | |
} | |
if($PSModuleInfo.ExportedFunctions.Count) | |
{ | |
$Tags += 'PSIncludes_Function' | |
$Tags += $PSModuleInfo.ExportedFunctions.Keys | Microsoft.PowerShell.Core\ForEach-Object { | |
"PSFunction_$_" | |
} | |
} | |
$Tags += $PSModuleInfo.ExportedCommands.Keys | Microsoft.PowerShell.Core\ForEach-Object { | |
"PSCommand_$_" | |
} | |
} | |
$dscResourceNames = Get-ExportedDscResources -PSModuleInfo $PSModuleInfo | |
if($dscResourceNames) | |
{ | |
$Tags += 'PSIncludes_DscResource' | |
$Tags += $dscResourceNames | Microsoft.PowerShell.Core\ForEach-Object { | |
"PSDscResource_$_" | |
} | |
} | |
$RoleCapabilityNames = Get-AvailableRoleCapabilityName -PSModuleInfo $PSModuleInfo | |
if($RoleCapabilityNames) | |
{ | |
$Tags += 'PSIncludes_RoleCapability' | |
$Tags += $RoleCapabilityNames | Microsoft.PowerShell.Core\ForEach-Object { | |
"PSRoleCapability_$_" | |
} | |
} | |
# Populate the module dependencies elements from RequiredModules and | |
# NestedModules properties of the current PSModuleInfo | |
$DependentModuleDetails = @() | |
$requiredModules = @() | |
#$requiredModules += $PSModuleInfo.RequiredModules | |
#$requiredModules += $PSModuleInfo.NestedModules | |
if ($ModuleManifestHashTable.RequiredModules) | |
{ | |
$requiredModules += $ModuleManifestHashTable.RequiredModules | |
} | |
if ($ModuleManifestHashTable.NestedModules) | |
{ | |
$requiredModules += $ModuleManifestHashTable.NestedModules | |
} | |
Write-Verbose "Total dependent modules: $($requiredModules.count)" | |
Foreach ($requiredModule in $requiredModules) | |
{ | |
$DependentModuleDetail = @{} | |
if($requiredModule.GetType().ToString() -eq 'System.Collections.Hashtable') | |
{ | |
$ModuleName = $requiredModule.ModuleName | |
Write-Verbose "Processing dependency module '$ModuleName'" | |
if($requiredModule.Keys -Contains 'RequiredVersion') | |
{ | |
Write-Verbose "'$ModuleName': Required version: $($requiredModule.RequiredVersion)" | |
$DependentModuleDetail.add('RequiredVersion', $requiredModule.RequiredVersion) | |
} | |
elseif($requiredModule.Keys -Contains 'ModuleVersion') | |
{ | |
Write-Verbose "$ModuleName': Module version: $($requiredModule.ModuleVersion)" | |
$DependentModuleDetail.add('ModuleVersion', $requiredModule.ModuleVersion) | |
} | |
} | |
else | |
{ | |
# Just module name was specified | |
Write-Verbose "$ModuleName': Module version not specified." | |
$ModuleName = $requiredModule.ToString() | |
} | |
$DependentModuleDetail.add('Name', $ModuleName) | |
$DependentModuleDetails += $DependentModuleDetail | |
} | |
$dependencies = @() | |
ForEach($Dependency in $DependentModuleDetails) | |
{ | |
$ModuleName = $Dependency.Name | |
$VersionString = $null | |
# Version format in NuSpec: | |
# "[2.0]" --> (== 2.0) Required Version | |
# "2.0" --> (>= 2.0) Minimum Version | |
# | |
# When only MaximumVersion is specified in the ModuleSpecification | |
# (,1.0] = x <= 1.0 | |
# | |
# When both Minimum and Maximum versions are specified in the ModuleSpecification | |
# [1.0,2.0] = 1.0 <= x <= 2.0 | |
if($Dependency.Keys -Contains 'RequiredVersion') | |
{ | |
$VersionString = "[$($Dependency.RequiredVersion)]" | |
} | |
elseif($Dependency.Keys -Contains 'ModuleVersion') | |
{ | |
$VersionString = "$($Dependency.ModuleVersion)" | |
} | |
if ([System.string]::IsNullOrWhiteSpace($VersionString)) | |
{ | |
$dependencies += "<dependency id='$($ModuleName)'/>" | |
} | |
else | |
{ | |
$dependencies += "<dependency id='$($ModuleName)' version='$($VersionString)' />" | |
} | |
} | |
# Populate the nuspec elements | |
$nuspec = @" | |
<?xml version="1.0"?> | |
<package > | |
<metadata> | |
<id>$(Get-EscapedString -ElementValue "$Name")</id> | |
<version>$($Version)</version> | |
<authors>$(Get-EscapedString -ElementValue "$Author")</authors> | |
<owners>$(Get-EscapedString -ElementValue "$CompanyName")</owners> | |
<description>$(Get-EscapedString -ElementValue "$Description")</description> | |
<releaseNotes>$(Get-EscapedString -ElementValue "$ReleaseNotes")</releaseNotes> | |
<requireLicenseAcceptance>$($requireLicenseAcceptance.ToString())</requireLicenseAcceptance> | |
<copyright>$(Get-EscapedString -ElementValue "$Copyright")</copyright> | |
<tags>$(if($Tags){ Get-EscapedString -ElementValue ($Tags -join ' ')})</tags> | |
$(if($LicenseUri) | |
{ | |
"<licenseUrl>$(Get-EscapedString -ElementValue "$LicenseUri")</licenseUrl>" | |
}) | |
$(if($ProjectUri) | |
{ | |
"<projectUrl>$(Get-EscapedString -ElementValue "$ProjectUri")</projectUrl>" | |
}) | |
$(if($IconUri) | |
{ | |
"<iconUrl>$(Get-EscapedString -ElementValue "$IconUri")</iconUrl>" | |
}) | |
<dependencies> | |
$dependencies | |
</dependencies> | |
</metadata> | |
</package> | |
"@ | |
try | |
{ | |
# Remove existing nuspec file | |
if($NuspecPath -and (Test-Path -Path $NuspecPath -PathType Leaf)) | |
{ | |
Write-Warning "Nuspec file '$NuspecPath' already exists. It will be overwritten." | |
Microsoft.PowerShell.Management\Remove-Item $NuspecPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false | |
} | |
Microsoft.PowerShell.Management\Set-Content -Value $nuspec -Path $NuspecPath -Force -Confirm:$false -WhatIf:$false | |
if($LASTEXITCODE -or -not $NuspecPath -or -not (Test-Path -Path $NuspecPath -PathType Leaf)) | |
{ | |
$message = "failed to create nuspec file '$NuspecPath'" | |
$errorId = 'FailedToCreateNuspecFile' | |
Write-Error -Message $message -ErrorId $errorId -Category InvalidOperation | |
return | |
} | |
} | |
finally | |
{ | |
if($NuspecPath -and (Test-Path -Path $NuspecPath -PathType Leaf) -and (Get-Content -LiteralPath $NuspecPath -Raw)) | |
{ | |
Write-Output -InputObject $NuspecPath | |
} | |
} | |
} | |
#endregion | |
#region main | |
Write-Output -InputObject "Generating .nuspec file based on PowerShell Module Manifest '$ManifestPath'" | |
$param = @{ | |
'ManifestPath' = $ManifestPath | |
} | |
If ($PSBoundParameters.ContainsKey('DestinationFolder')) | |
{ | |
$param.Add('DestinationFolder', $DestinationFolder) | |
} | |
$NuspecFile = New-NuSpecFile @param | |
If ($NuspecFile) | |
{ | |
Write-Output "Nuspec file created - '$NuspecFile'." | |
Write-Output "Done. " | |
} else { | |
Write-Error "Failed to create Nuspec file." | |
} | |
#endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment