Managing NuGet Packages with StudioShell

§ July 25, 2014 14:14 by beefarino |

stockvault-gift125211I’ve been getting a lot of questions about doing NuGet things in StudioShell.  It would seem simple enough: import the NuGet PowerShell package into the StudioShell environment, and use the functions defined there.  Unfortunately it’s not that simple.  The NuGet module is tightly bound to the Package Manager Console host in the module’s PSD1 definition file:

# ...
# Name of the Windows PowerShell host required by this module
PowerShellHostName = 'Package Manager Host'

# Minimum version of the Windows PowerShell host required by this module
PowerShellHostVersion = '1.2'
# ...

In other words, you won’t be able to import the NuGet module outside of the Package Manager Console.  This seems rather arbitrary to me, especially considering the fact that the module itself doesn’t do anything that mandates any specific host, just that the host environment contains the $dte variable reference to the Visual Studio Application object.  Anyhoodle, I digress, and shall save the rest of the rant for another post.

So how does one combine the awesome sauce of StudioShell with the utility of NuGet?  Well, you may not be able to bring NuGet into StudioShell, but you can certainly bring StudioShell to into the Package Manager Console.  Recent releases of StudioShell include specialized support for the Package Manager console.  This allows you to use the two in tandem, and this post describes some of the crazy things you can do.

At the 2014 NA PowerShell Summit, I did a talk on using the P2F project to develop providers.  At the start of that talk, I ran a single command in the PM console:

new-providerProject -name TypeProvider

The command automates the process of setting up a new P2F provider project; specifically, it does all of the following:

  1. Creates a new C# class library project.
  2. Adds an assembly reference to System.Management.Automation to the new project.
  3. Installs the P2F NuGet package into the newly created project.
  4. Modifies the project’s debug settings to launch PowerShell.exe with a command line that imports the project’s binary into the PowerShell session.
  5. Sets the new project as the solution’s current startup project.
  6. Enables NuGet package restore on the solution.

Here’s the function definition in its entirety:

import-module studioshell.provider
import-module studioshell.contrib

function new-providerProject( $name )
{
	# create the project
	new-item "dte:\solution\projects\$name" -type classlibrary -language csharp	
	
	# add necessary references
	new-item "dte:\solution\projects\$name\references" -type assembly -name System.Management.Automation
	
	# install the P2F nuget pacakge
	install-package "P2F" -project $name;	
	
	# configure the project settings
	$project = get-item "dte:\solution\projects\$name"
	$project.configurationmanager | foreach {
		$_.properties.item('startprogram').value = 
		  "c:\windows\system32\windowspowershell\v1.0\powershell.exe"
		$_.properties.item('startarguments').value = 
		  '-noexit -command "ls *.dll | ipmo"'
		$_.properties.item('startaction').value = 1
	}

	# set this project as the startup project
	$dte.solution.projects.item("StartupProject").value = $project.name;

	# enable nuget package restore
	enable-nugetPackageRestore;
}

The first two lines import the necessary StudioShell modules; StudioShell.Provider is the simplified NuGet distribution of the DTE provider, and StudioShell.Contrib is a community contribution module with useful wrappers around common StudioShell uses.

The first line of the function creates a new C# class library project:

# create the project
new-item "dte:\solution\projects\$name" -type classlibrary -language csharp

Here we use the common PowerShell item cmdlets against the StudioShell DTE provider, specifying a path under the solution’s projects tree where we want the project to be created.  The value for the type parameter often confounds people; that is, you may not be sure what value to use here.  StudioShell has your back – you can find a list of the project templates for various languages under the dte:/templates/projects path.

The next line modifies the assembly references for the new project:

# add necessary references
new-item "dte:\solution\projects\$name\references" -type assembly -name System.Management.Automation

Again, just using the standard new-item cmdlet at the right path does the trick.  At the project’s references folder, the type parameters can be “assembly”, “project”, or “com”, depending on the type of reference you’re adding.  The name parameter specifies the assembly name, project name, or COM ProgId to reference.

Next, we leverage the NuGet module to install the P2F package:

install-package "P2F" -project $name 

This command should look familiar if you’re a NuGet user.  If not, you’re using NuGet wrong and should feel bad.  This command does a lot of magic stuff – it pulls the P2F package from the main NuGet repository, modifies the project references, and so forth.  All that NuGet-ish stuff, in just one little command. 

Now comes an ugly part.  Whenever I’m making a new PowerShell provider, I want to set up the project debuggery so that it launches PowerShell with a specific command line.  In the UI, I would go into the project properties debug tab and make the necessary modifications.  In the PM console, I accomplish the same thing as follows:

$project = get-item "dte:\solution\projects\$name"
$project.configurationmanager | foreach {
	$_.properties.item('startprogram').value = "c:\windows\system32\windowspowershell\v1.0\powershell.exe"
	$_.properties.item('startarguments').value = '-noexit -command "ls *.dll | ipmo"'
	$_.properties.item('startaction').value = 1
}

Honestly it took me a little while to find the specific property names I needed to modify.  And I see how ugly and unhelpful this code is, so I’ve added project properties to the DTE drive topology for the upcoming release of StudioShell.  So, HOORAY ME and you owe me a beer if you’re reading this.

And while I’m in the ugly bits, I set the solution-level property that marks this project as the startup project:

$dte.solution.projects.item("StartupProject").value = $project.name;

Again, this code is going away in favor of new DTE hives for solution properties.  Again, more beer is owed by you to me.

Finally, we have a little extra NuGet magic.  This one’s been on my plate to share for a while, but special thanks go to Attila Hajdrik for kicking me in the seat to get it done.  This last command will enable the NuGet package restore for the solution, if it is not already enabled:

# enable nuget package restore
enable-nugetPackageRestore;

The code behind this command taps in to the bottomless well of woe that is the Visual Studio service provider model.  I’m not going into the details of the implementation, but you can see Attila's approach in this gist.  Because I do this enough that I want to keep it simple to automate, I’ve added Attila’s implementation to the StudioShell.Contrib project.

So there you have it: using StudioShell to manage projects, and NuGet to manage package references, all in one big automated pile of bytes.



StudioShell.DependencyExample Nuget Package

§ February 13, 2014 17:49 by beefarino |

As promised, I’ve pushed a simple example of using StudioShell.Provider as a Nuget package dependency.  You can pull the StudioShell.DependencyExample for the code.  At the moment this package does one thing – it adds a Solution Module to your current solution if one is not already present.

Let’s take a closer look at what this package actually does and how it does it…

The Nuspec File

Inside of the StudioShell.DependencyExample nuspec file, I’ve listed StudioShell.Provider as a dependency package:

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>StudioShell.DependencyExample</id>
    <version>1.0</version>
    <authors>beefarino</authors>
    <owners>beefarino</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>A simple demonstration of using StudioShell.Provider automation features from your Nuget packages</description>
    <tags>VisualStudio StudioShell DTE automation nuget</tags>
    <dependencies>
      <dependency id="StudioShell.Provider" version="1.6.2" />
    </dependencies>
  </metadata>
</package>

This ensures that the StudioShell PSDTE provider is loaded into the package manager console session before installing my example package.  This will allows me to leverage the DTE drive from my package scripts.

The Init.ps1 File

The init.ps1 files makes use of the DTE drive to spelunker the solution, project items, and folders.

The first few lines of the init.ps1 file get the currently open Visual Studio solution.  The $slnName variable is set to the “name” of the solution, which is determined as the filename without the .sln extension.  The $modulePath variable is set to the location of the solution module for this solution. 

#
#   Copyright (c) 2014 Code Owls LLC, All Rights Reserved.
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
param($installPath, $toolsPath, $package, $project)

# add a new StudioShell solution module to the solution if one does not already exist

$sln = get-item dte:/solution;
$slnName = ( $sln.FullName | split-path -leaf ) -replace '\..+$','';
$modulePath = $sln.FullName -replace '\.sln$','.psm1';
#...

Next we check if a solution module does not already exist at this path; if we find one, we simply return out of the script:

#...
if( test-path $modulePath )    
{
  return;
}
#...

And if no solution file exists, we go ahead and create one using a little powershell here-string magic:

#...
@"
# Example StudioShell solution module for ${slnName}
# 

`$menuItems = @(
    # list any menu items your module adds here
    #
    # e.g.:
    new-item 'dte:/commandbars/menu bar/help' `
	 -name 'about my module' `
	-value { 'Module ${slnName}' | `
    out-outputpane; invoke-item dte:/windows/output; }
);

# this function is called automatically when your solution is unloaded
function unregister-${slnName}
{
    # remove any added menu items;
    `$menuItems | remove-item;
}
"@ | out-file -filepath $modulePath;
#...

Once the solution module file is created, we add it to the current solution items.  First we create a Solution Items folder if one does not yet exist:

#...
#create a solution items project folder if necessary
if( -not ( test-path 'dte:/solution/projects/solution items' ) )
{
    new-item -path 'dte:/solution/projects/solution items' -type folder;
}
#...

After we ensure we have a Solution Items folder, we simply add the existing file to it:

#...
#add the solution module to the solution items project folder
new-item -path 'dte:/solution/projects/solution items' -filepath $modulePath;

Looking Ahead

I’ll keep updating the StudioShell.DependencyExample package as I find new and interesting things to do with StudioShell and Nuget.  If you come up with your own, please let me know about them!



StudioShell 1.6.2 is all About Better Nuget Support

§ February 13, 2014 16:40 by beefarino |

ssnugetI just pushed a point release of StudioShell.  It contains a few fixes related to autocomplete, but the majority of the code changes are related to being able to easily leverage the DTE drive from Nuget packages and the Package Manager console in Visual Studio.

Up until this point, you had to install the entire StudioShell environment to be able to use its features from the Package Manager console.  This was problematic because the full install for StudioShell is not straightforward or simple, and most of the install-related issues reported for the project stemmed from people trying to leverage it from the Package Manager console.

This latest release remedies this by breaking the StudioShell project into two unique distributions:

  1. StudioShell: the complete StudioShell environment;
  2. StudioShell.Provider: the StudioShell DTE PowerShell provider.

Here’s a breakdown of how the features between the two distributions differ:

Feature StudioShell StudioShell.Provider
DTE Drive

*

*

Solution Modules

*

*

Custom Scripted Menu Items

*

*

Data Panes & Visualizations

*

 
Custom Host Settings

*

 
Custom Profile Scripts

*

 
Custom Console

*

 

In a nutshell, StudioShell.Provider isolates the main gem of StudioShell automation – the PSDTE PowerShell provider.  The host-level features, such as visualizations and the custom StudioShell profile script, are only available in the full StudioShell install.

The reason I’m doing this is simple: I want people to be able to use StudioShell in their Nuget packages to manipulate Visual Studio.  I’m working on a simple demonstration package that will show off a bit of what you’ll be able to do from your Nuget packages if you add a dependcy on StudioShell.Provider and will publish it as soon as it’s ready.



StudioShell 1.6 Supports VS 2013

§ January 28, 2014 23:18 by beefarino |

ohaiGreat news for StudioShell lovers…

This evening I pushed a new build of StudioShell that includes support for Visual Studio 2013.  The new build is available through the usual channels:

  • Download the installer from CodePlex
  • Install using NuGet or Chocolately
  • Clone the code and invoke the install psake task:
hg clone  https://hg.codeplex.com/studioshell
cd studioshell
import-module psake
invoke-psake install

As always, report any issues or requests using the Issue Tracker.

Happy coding!