Archive

Posts Tagged ‘Package’

Have Your NuGet Package Install Itself As A Development Dependency

September 18th, 2013 3 comments

The Problem

I recently blogged about a NuGet package I made that allows you to easily turn your own projects into a NuGet package, making it easy to share your work with the world.  One problem I ran into with this was that if somebody used my NuGet package to create their package, their NuGet package listed my NuGet package as a dependency.  This meant that when they distributed their package to others, it would install both their package and mine.  Obviously this is undesirable, since their library has no dependency on my package; my package was meant purely to help them with the development process.

Unfortunately there wasn’t much I could do about this; that is, until the release of NuGet 2.7 which came out a few weeks ago.  You can see from the release notes that they added a new developmentDependency attribute that can be used.  This made things a bit better because it allowed users who installed my package to go into their project’s packages.config file, find the element corresponding to my package, and add the developmentDependency=”true” attribute to it.

So this was better, but still kinda sucked because it required users to do this step manually, and most of them likely aren’t even aware of the problem or that there was a fix for it.  When users (and myself) install a package they want it to just work; which is why I created a fix for this.

 

The Fix

Update – As of NuGet 2.8 there is a built-in way to do the fix below. See this post for more info.

The nice thing about NuGet packages is that you can define PowerShell scripts that can run when users install and uninstall your packages, as is documented near the bottom of this page.  I’ve created a PowerShell script that will automatically go in and adjust the project’s packages.config file to mark your package as a development dependency.  This means there is no extra work for the user to do.

The first thing you need to do (if you haven’t already) is include an Install.ps1 script in your NuGet package’s .nuspec file.  If you don’t currently use a .nuspec file, check out this page for more information.  I also include a sample .nuspec file at the end of this post for reference.  The line to add to your .nuspec file will look something like this:

<file src=”NuGetFiles\Install.ps1″ target=”tools\Install.ps1″ />

and then the contents of Install.ps1 should look like this:

param($installPath, $toolsPath, $package, $project)

# Edits the project's packages.config file to make sure the reference to the given package uses the developmentDependency="true" attribute.
function Set-PackageToBeDevelopmentDependency($PackageId, $ProjectDirectoryPath)
{
    function Get-XmlNamespaceManager($XmlDocument, [string]$NamespaceURI = "")
    {
        # If a Namespace URI was not given, use the Xml document's default namespace.
	    if ([string]::IsNullOrEmpty($NamespaceURI)) { $NamespaceURI = $XmlDocument.DocumentElement.NamespaceURI }	

	    # In order for SelectSingleNode() to actually work, we need to use the fully qualified node path along with an Xml Namespace Manager, so set them up.
	    [System.Xml.XmlNamespaceManager]$xmlNsManager = New-Object System.Xml.XmlNamespaceManager($XmlDocument.NameTable)
	    $xmlNsManager.AddNamespace("ns", $NamespaceURI)
        return ,$xmlNsManager		# Need to put the comma before the variable name so that PowerShell doesn't convert it into an Object[].
    }

    function Get-FullyQualifiedXmlNodePath([string]$NodePath, [string]$NodeSeparatorCharacter = '.')
    {
        return "/ns:$($NodePath.Replace($($NodeSeparatorCharacter), '/ns:'))"
    }

    function Get-XmlNodes($XmlDocument, [string]$NodePath, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.')
    {
	    $xmlNsManager = Get-XmlNamespaceManager -XmlDocument $XmlDocument -NamespaceURI $NamespaceURI
	    [string]$fullyQualifiedNodePath = Get-FullyQualifiedXmlNodePath -NodePath $NodePath -NodeSeparatorCharacter $NodeSeparatorCharacter

	    # Try and get the nodes, then return them. Returns $null if no nodes were found.
	    $nodes = $XmlDocument.SelectNodes($fullyQualifiedNodePath, $xmlNsManager)
	    return $nodes
    }

    # Get the path to the project's packages.config file.
    Write-Debug "Project directory is '$ProjectDirectoryPath'."
    $packagesConfigFilePath = Join-Path $ProjectDirectoryPath "packages.config"

    # If we found the packages.config file, try and update it.
    if (Test-Path -Path $packagesConfigFilePath)
    {
        Write-Debug "Found packages.config file at '$packagesConfigFilePath'."

        # Load the packages.config xml document and grab all of the <package> elements.
        $xmlFile = New-Object System.Xml.XmlDocument
        $xmlFile.Load($packagesConfigFilePath)
        $packageElements = Get-XmlNodes -XmlDocument $xmlFile -NodePath "packages.package"

        Write-Debug "Packages.config contents before modification are:`n$($xmlFile.InnerXml)"

        if (!($packageElements))
        {
            Write-Debug "Could not find any <package> elements in the packages.config xml file '$packagesConfigFilePath'."
            return
        }

        # Add the developmentDependency attribute to the NuGet package's entry.
        $packageElements | Where-Object { $_.id -eq $PackageId } | ForEach-Object { $_.SetAttribute("developmentDependency", "true") }

        # Save the packages.config file back now that we've changed it.
        $xmlFile.Save($packagesConfigFilePath)
    }
    # Else we coudn't find the packages.config file for some reason, so error out.
    else
    {
        Write-Debug "Could not find packages.config file at '$packagesConfigFilePath'."
    }
}

# Set this NuGet Package to be installed as a Development Dependency.
Set-PackageToBeDevelopmentDependency -PackageId $package.Id -ProjectDirectoryPath ([System.IO.Directory]::GetParent($project.FullName))

And that’s it.  Basically this script will be ran after your package is installed, and it will parse the project’s packages.config xml file looking for the element with your package’s ID, and then it will add the developmentDependency=”true” attribute to that element.  And of course, if you want to add more code to the end of the file to do additional work, go ahead.

So now your users won’t have to manually edit their packages.config file, and your user’s users won’t have additional, unnecessary dependencies installed.

 

More Info

As promised, here is a sample .nuspec file for those of you that are not familiar with them and what they should look like.  This is actually the .nuspec file I use for my package mentioned at the start of this post.  You can see that I include the Install.ps1 file near the bottom of the file.

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>CreateNewNuGetPackageFromProjectAfterEachBuild</id>
    <version>1.4.2</version>
    <title>Create New NuGet Package From Project After Each Build</title>
    <authors>Daniel Schroeder,iQmetrix</authors>
    <owners>Daniel Schroeder,iQmetrix</owners>
    <licenseUrl>https://newnugetpackage.codeplex.com/license</licenseUrl>
    <projectUrl>https://newnugetpackage.codeplex.com/wikipage?title=NuGet%20Package%20To%20Create%20A%20NuGet%20Package%20From%20Your%20Project%20After%20Every%20Build</projectUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Automatically creates a NuGet package from your project each time it builds. The NuGet package is placed in the project's output directory.
	If you want to use a .nuspec file, place it in the same directory as the project's project file (e.g. .csproj, .vbproj, .fsproj).
	This adds a PostBuildScripts folder to your project to house the PowerShell script that is called from the project's Post-Build event to create the NuGet package.
	If it does not seem to be working, check the Output window for any errors that may have occurred.</description>
    <summary>Automatically creates a NuGet package from your project each time it builds.</summary>
    <releaseNotes>Updated to use latest version of New-NuGetPackage.ps1.</releaseNotes>
    <copyright>Daniel Schroeder 2013</copyright>
    <tags>Auto Automatic Automatically Build Pack Create New NuGet Package From Project After Each Build On PowerShell Power Shell .nupkg new nuget package NewNuGetPackage New-NuGetPackage</tags>
  </metadata>
  <files>
    <file src="..\New-NuGetPackage.ps1" target="content\PostBuildScripts\New-NuGetPackage.ps1" />
    <file src="Content\NuGet.exe" target="content\PostBuildScripts\NuGet.exe" />
    <file src="Content\BuildNewPackage-RanAutomatically.ps1" target="content\PostBuildScripts\BuildNewPackage-RanAutomatically.ps1" />
    <file src="Content\UploadPackage-RunManually.ps1" target="content\PostBuildScripts\UploadPackage-RunManually.ps1" />
    <file src="Content\UploadPackage-RunManually.bat" target="content\PostBuildScripts\UploadPackage-RunManually.bat" />
    <file src="tools\Install.ps1" target="tools\Install.ps1" />
    <file src="tools\Uninstall.ps1" target="tools\Uninstall.ps1" />
  </files>
</package>

 

Happy coding!

PowerShell Needs A Centralized Package Management Solution

September 9th, 2013 4 comments

TL;DR – PowerShell needs centralized package management.  Please go up-vote this request to have it added to PowerShell.


I love PowerShell, and I love writing reusable PowerShell modules.  They work great when I am writing scripts for myself.  The problem comes in when I write a script that depends on some modules, and I then want to share that script with others.  I basically have 2 options:

  1. Track down all of the module files that the script depends on, zip them all up, and send them to the recipient along with instructions such as, “Navigate to this folder on your PC, create a new folder with this name, copy file X to this location, rinse, repeat…”.
  2. Track down all of the module files that the script depends on and copy-paste their contents directly into the top of the script file, so I just send the user one very large file.

Neither of these solutions are ideal.  Maybe I’m missing something?  In my opinion, PowerShell really needs centralized package management; something similar to Ruby Gems would be great.  Basically a website where users can upload their scripts with a unique ID, and then in their PowerShell script at the top of the file just list the modules that the script depends on.  If the modules are not installed on that PC yet, then they would automatically be downloaded and installed.  This would make PowerShell so much more convenient, and I believe it would help drive more users to write reusable modules and avoid duplicating modules that have already been written (likely better) by others.

In order for this to work though, it has to be baked directly into the PowerShell architecture by the PowerShell team; it’s not something that a 3rd party could do.  So to try and bring this feature request to Microsoft’s attention, I have create a Suggestion on the MS Connect site.  Please go up-vote it.

Before thinking to create a feature request for this (duh), I spammed some of my favourite PowerShell Twitter accounts (@JamesBru @ShayLevy @dfinke @PowerShellMag @StevenMurawski @JeffHicks @ScriptingGuys) to bring it to their attention and get their thoughts; sorry about that guys!  This blog’s comments are a better forum than Twitter for discussing these types of things.

If you have thoughts on centralized package management for PowerShell, or have a better solution for dealing with distributing scripts that depend on modules, please leave a comment below. Thanks.

Happy coding!

[Update]

While PowerShell does not provide a native module management solution, Joel “Jaykul” Bennett has written one and all of the modules are hosted at http://poshcode.org/, although I believe it can download modules from other sources as well (e.g. GitHub or any other URL).  One place that it cannot download files from is CodePlex since CodePlex does not provide direct download links to the latest versions of files or to their download links (it is done through Javascript).  Please go up-vote this issue and this issue to try and get this restriction removed.

Automatically Create Your Project’s NuGet Package Every Time It Builds, Via NuGet

June 22nd, 2013 15 comments

So you’ve got a super awesome library/assembly that you want to share with others, but you’re too lazy to actually use NuGet to package it up and upload it to the gallery; or maybe you don’t know how to create a NuGet package and don’t have the time or desire to learn.  Well, my friends, now this can all be handled for you automatically.

A couple weeks ago I posted about a new PowerShell script that I wrote and put up on CodePlex, called New-NuGetPackage PowerShell Script, to make creating new NuGet packages quick and easy.  Well, I’ve taken that script one step further and use it in a new NuGet package called Create New NuGet Package From Project After Each Build (real creative name, right) that you can add to your Visual Studio projects.  The NuGet package will, you guessed it, pack your project and its dependencies up into a NuGet package (i.e. .nupkg file) and place it in your project’s output directory beside the generated dll/exe file.  Now creating your own NuGet package is as easy as adding a NuGet package to your project, which if you’ve never done before is dirt simple.

I show how to add the NuGet package to your Visual Studio project in the New-NuGetPackage PowerShell Script documentation (hint: search for “New NuGet Package” (include quotes) to find it in the VS NuGet Package Manager search results), as well as how you can push your package to the NuGet Gallery in just a few clicks.

Here’s a couple screenshots from the documentation on installing the NuGet Package:

NavigateToManageNugetPackages   InstallNuGetPackageFromPackageManager

Here you can see the new PostBuildScripts folder it adds to your project, and that when you build your project, a new .nupkg file is created in the project’s Output directory alongside the dll/exe.

FilesAddedToProject     NuGetPackageInOutputDirectory

So now that packaging your project up in a NuGet package can be fully automated with about 30 seconds of effort, and you can push it to the NuGet Gallery in a few clicks, there is no reason for you to not share all of the awesome libraries you write.

Happy coding!

Create and publish your NuGet package in one click with the New-NuGetPackage PowerShell script

June 7th, 2013 No comments

I’ve spent a good chunk of time investigating how nuget.exe works and creating a PowerShell script called New-NuGetPackage to make it dirt simple to pack and push new NuGet packages.

Here’s a list of some of the script’s features:

  • Create the .nupkg package file and optionally push the package to the NuGet Gallery (or a custom gallery).
  • Can be ran from Windows Explorer (i.e. double-click it) or called via PowerShell if you want to be able to pass in specific parameters or suppress prompts.
  • Can prompt user for version number and release notes (prompts are prefilled with previous version number and release notes) or can suppress all prompts.

This makes packing and pushing your NuGet packages quick and easy, whether doing it manually or integrating it into your build system.  Creating NuGet packages wasn’t overly complicated before, but this makes it even simpler and less tedious.

Go to the codeplex page to download the script and start automating your NuGet package creating today.  The codeplex documentation describes the script in much more detail, as well as step by step instructions on how to get setup to start using it.

[UPDATE] I have also used this script in a new NuGet package that will automatically create a NuGet package for your own projects without you having to do anything. Read about it here. [/UPDATE]

 

Additional NuGet Information

During my investigation I compiled a list of what happens when doing “nuget spec” and “nuget pack” against the various different file types (e.g. dll vs. project vs. nuspec).  Someone else may find this information useful, so here it is:

Spec a Project or DLL directly (e.g. "nuget spec PathToFile"):
- Creates a partial .nuspec; still has placeholder info for some fields (e.g. Id, Dependencies).
- Creates [full file name with extension].nuspec file.
- The generated .nuspec file is meant to still be manually updated before making a package from it.

// TestProject.csproj.nuspec
<?xml version="1.0"?>
<package >
  <metadata>
    <id>C:\dev\TFS\RQ\Dev\Tools\DevOps\New-NuGetPackage\TestProject\TestProject\TestProject.csproj</id>
    <version>1.0.0</version>
    <authors>Dan Schroeder</authors>
    <owners>Dan Schroeder</owners>
    <licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl>
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Package description</description>
    <releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
    <copyright>Copyright 2013</copyright>
    <tags>Tag1 Tag2</tags>
    <dependencies>
      <dependency id="SampleDependency" version="1.0" />
    </dependencies>
  </metadata>
</package>
=====================================================================
Spec a DLL using "nuget spec" from the same directory:
- Creates a partial .nuspec; still has placeholder info for some fields (e.g. Id, Dependencies).
- Creates "Package.nuspec" file.
- The generated .nuspec file is meant to still be manually updated before making a package from it.

// Package.nuspec
<?xml version="1.0"?>
<package >
  <metadata>
    <id>Package</id>
    <version>1.0.0</version>
    <authors>Dan Schroeder</authors>
    <owners>Dan Schroeder</owners>
    <licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl>
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Package description</description>
    <releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
    <copyright>Copyright 2013</copyright>
    <tags>Tag1 Tag2</tags>
    <dependencies>
      <dependency id="SampleDependency" version="1.0" />
    </dependencies>
  </metadata>
</package>
=====================================================================
Spec a Project using "nuget spec" from the same directory:
- Creates a template .nuspec using the proper properties and dependencies pulled from the file.
- Creates [file name without extension].nuspec file.
- The generated .nuspec file can be used to pack with, assuming you are packing the Project and not the .nuspec directly.

// TestProject.nuspec
<?xml version="1.0"?>
<package >
  <metadata>
    <id>$id$</id>
    <version>$version$</version>
    <title>$title$</title>
    <authors>$author$</authors>
    <owners>$author$</owners>
    <licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl>
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>$description$</description>
    <releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
    <copyright>Copyright 2013</copyright>
    <tags>Tag1 Tag2</tags>
  </metadata>
</package>
=====================================================================
Pack a Project (without accompanying template .nuspec):
- Does not generate a .nuspec file; just creates the .nupkg file with proper properties and dependencies pulled from project file.
- Throws warnings about any missing data in the project file (e.g. Description, Author), but still generates the package.

=====================================================================
Pack a Project (with accompanying template .nuspec):
- Expects the [file name without extension].nuspec file to exist in same directory as project file, otherwise it doesn't use a .nuspec file for the packing.
- Throws errors about any missing data in the project file if the .nuspec uses tokens (e.g. $description$, $author$) and these aren't defined in the project, so the package is not generated.

=====================================================================
Cannot pack a .dll directly

=====================================================================
Pack a .nuspec:
- Creates the .nupkg file with properties and dependencies defined in .nuspec file.
- .nuspec file cannot have any placeholder values (e.g. $id$, $version$).