Nuget packages are a popular way to distribute .NET libraries both internally and externally. Creating a nuget package from a project source is easy.
In the command window, navigate to the project directory
nuget spec
nuget pack <Project>.csproj or Nuget pack <Project>.vbproj
That is quite straightforward and easy to script. However this process has two assumptions:
- The Project file has all the details specified, like Assembly Company and Assembly Description
- All the files needed in the nuget package are the result of building the project
Let’s get around the 2 assumptions with a small Powershell script and create our package dynamically.
Let’s get started
- Open your favourite editor for scripting (a program like Atom is helpful as it recognises Powershell scripts syntax)
- Type in the two nuget commands, highlighted earlier (here we assume the script is in the project directory)
# To make the script re-useable let’s fetch the project file name automatically
$projectFile = get-childitem . -Path *.csproj | select -limit 1
# Since we are in the project directory we can create the nuget specification file directly
nuget spec
# Package the project. Note that $projectFile is a FileInfo object so we need to get the name property for the command
nuget pack $projectFile.Name
Getting around Assumption 1: Company and Description info are in the assembly information
The following code treats the nuspec file created by ‘nuget spec’ as an XML Document. If you need help with opening the file as XML Document, please refer to the full code at the end of the article.
- Get the contents of the
AssemblyInfo.cs
$assemblyInfo = (Get-Content Properties\AssemblyInfo.cs)
- Next retrieve the AssemblyCompany info from the file
# Using regular expression we get the line containing the information required
$author = ($assemblyInfo -match 'AssemblyCompany\(".*"\)')
# Next we need to obtain the information within the double quotes. This can be done through regex but for this example we use the split command.
$author = $author -split ('"')
# The split command returns an array of 3 items, zero-indexed. The first entry is the AssemblyCompany text and the second entry in the array is the actual information in the double quotes.
$author = $author[1]
- Now check the
$author
variable for any contents. If it is an empty string then change the Author element from the nuget token to a fixed string.If ($author -eq "") {
$authorNode = $nuspecXml.SelectSingleNode("//authors")
$authorNode.InnerText = "Author"
$ownersNode = $nuspecXml.SelectSingleNode("//owners")
$ownersNode.InnerText = "Owner"
}
Note: The nuspec XML has two entries, one is owners and the other is authors. These normally point to the same nuspec token
$authors
. However they can be set to separate values if required. - The same three steps can be performed for the Description, and any other element that needs to be verified for empty strings. The code below shows the instructions for the Description field.
$description = ($assemblyInfo -match 'AssemblyDescription\(".*"\)')
$description = $description -split ('"')
$description = $description[1]
If ($description -eq "") {
$descriptionNode = $nuspecXml.SelectSingleNode("//description")
$descriptionNode.InnerText = "My Assembly Description"
}
Getting around Assumption 2: All files are the result of project build
Adding files to the nuget specification files in the nuspec file is done by creating new XML elements in the file. The below steps add only 1 folder to the package but the code can be repeated multiple times to add as many folders or files as required.
- Create the
<files>
XML node which will contain the references to the folders and files outside the project build that are to be added to the nuget package$filesNode = $nuspecXml.CreateNode("element", "files", "")
- The
<files>
XML node is a list of<file>
nodes, so the new<file>
node needs to be created$fileNode = $nuspecXml.CreateNode("element", "file", "")
- Looking at the nuget documentation the
<file>
XML element needs two attributes the src and target attributes. These are created outside the<file>
node and then attached in step 4.
For the purpose of this article it is assumed that the original files are stored in the location nonProjectFiles. Note the location is relative to the nuspec file$fileSrcAttribute = $nuspecXml.CreateAttribute(“src")
$fileSrcAttribute.Value = "..\nonProjectFiles\**\*"
$fileTargetAttribute = $nuspecXml.CreateAttribute("target")
$fileTargetAttribute.Value = "content\additionalFiles\$assemblyName"
- Now the attributes are bound to the
<file>
node$fileNode.SetAttributeNode($fileSrcAttribute)
$fileNode.SetAttributeNode($fileTargetAttribute)
- The final step is to append the file node to the files node, and the files node to the nuspec file.
$filesNode.AppendChild($fileNode)
$nuspecXml.LastChild.AppendChild($filesNode)
Note: To add more files or folders to the
<files>
tag, one has to add more child nodes to the$filesNode
variable.
So putting this altogether the script look like
get-childitem . -Path *.csproj | select -limit 1 | % {
nuget spec -f # The -f is to force file recreation
$assemblyName = [System.IO.Path]::GetFileNameWithoutExtension($_.Name)
$nuspec = "$assemblyName.nuspec"
# Open nuspec file as an XML Document
[xml]$nuspecXml = (Get-Content $nuspec)
# Assumption 1 workaround
$author = ($assemblyInfo -match 'AssemblyCompany\(".*"\)')
$author = $author -split ('"')
$author = $author[1]
If ($author -eq "") {
$authorNode = $nuspecXml.SelectSingleNode("//authors")
$authorNode.InnerText = "Authors"
$ownersNode = $nuspecXml.SelectSingleNode("//owners")
$ownersNode.InnerText = "Owners"
}
$description = ($assemblyInfo -match 'AssemblyDescription\(".*"\)')
$description = $description -split ('"')
$description = $description[1]
If ($description -eq "") {
$descriptionNode = $nuspecXml.SelectSingleNode("//description")
$descriptionNode.InnerText = "My Module Description"
}
# Assumption 2 workaround
if (Test-Path ..\nonProjectFiles) {
$filesNode = $nuspecXml.CreateNode("element", "files", "")
$fileNode = $nuspecXml.CreateNode("element", "file", "")
$fileSrcAttribute = $nuspecXml.CreateAttribute("src")
$fileSrcAttribute.Value = "..\nonProjectFiles\**\*"
$fileNode.SetAttributeNode($fileSrcAttribute)
$fileTargetAttribute = $nuspecXml.CreateAttribute("target")
$fileTargetAttribute.Value = "content\additionalFiles\$assemblyName"
$fileNode.SetAttributeNode($fileTargetAttribute)
$filesNode.AppendChild($fileNode)
$nuspecXml.LastChild.AppendChild($filesNode)
}
$nuspecXml.Save($_.Directory.FullName + "\" + $nuspec)
# Create the nuget package
nuget pack $_.Name
}
Note: Code can be downloaded from GitHub: https://github.com/kdemanuele/Nuget-Powershell
References
- Nuget Specifications Reference
- Creating and publishing a package
- Nuget Pack CSProoj using Nuspec
- How Nuget chooses the nuspec file when building from a project file
- Calling XMLDocument methods in Powershell
- MSDN References to XML Elements
- https://msdn.microsoft.com/en-us/library/system.xml.xmldocument(v=vs.110).aspx
- https://msdn.microsoft.com/en-us/library/system.xml.xmlattribute(v=vs.110).aspx
- https://msdn.microsoft.com/en-us/library/sefzz8zs(v=vs.110).aspx
- https://msdn.microsoft.com/en-us/library/system.xml.xmlnode(v=vs.110).aspx
- https://msdn.microsoft.com/en-us/library/system.xml.xmlnodelist(v=vs.110).aspx