Posts tagged 'Powershell'

22

April 2013

PowerShell function to launch Kiln/BitBucket/Google Code/etc. for current Mercurial repo from command line

A small, but useful, PowerShell function that I have in my PowerShell profile that reads the default repo path from .hg\hgrc and launches it in the default browser. This means after I've done hg push I can just ype kiln to quickly get to the repo page to raise code reviews, etc.

06

May 2012

Free dependency graph generation using PowerShell and yUML

I don't like the idea of paying for (or installing) a tool to just show me some simple relationships between my Visual Studio projects, but it's something that is pretty useful when trying to tidy up a huge legacy codebase.

I whipped together a quick PowerShell script that spits out project references in a format ready for pasting into yuml.me to draw a chart:

The output will look something like this:

09

May 2011

Adding Tab-Expansion to Andrew Nurse's PSGet (NuGet Powershell Module)

If you subscribe to my blog or follow me on Twitter you'll probably know I'm quite a fan of NuGet. Recently I was playing around with Andrew Nurse's PSGet module that wraps NuGet.exe for PowerShell (which, by the way, is an awesome idea, and should be added as built-in functionality!). I decided a good way to learn a little more about PowerShell would be to try and add Tab Expansion to PSGet, similar to the functionality in the Visual Studio Package Manager Console.

Note: I forked Andrew Nurse's PSGet project to make these changes, though I'll see if he's interested in pulling them back into his, and updating the NuGet package. That way people will have a single source for PowerShell/NuGet goodness. Until then, if you want my Tab Expansion, you can grab the module from here.

Disclaimer: I've only been using PowerShell for around a week, so I'm sure there are much better ways to do what I've done. I did what I could with the (little) knowledge I have and some Googling. If you can see better ways of doing things, please let me know so I can learn, and update my code (send me a pull request on Bitbucket if you want).

Show me the code!

Tab Expansion in PowerShell works via the TabExpansion method. This function is called and provided with the current line in addition to the last word typed. I couldn't find a way to hook TabExpansion for just the commands we're interested in, so I had to hijack the whole lot. This doesn't seem to be a problem, as falling back to the default behaviour seemed pretty simple by keeping a copy of the function:

# Copy the current Tab Expansion function so we can fall back to it if we're not supposed to handle a command
Copy Function:\TabExpansion Function:\OriginalTabExpansion

function TabExpansion([string] $line, [string] $lastword)
{
    # Only run for "Get-PSPackage" or "Install-PSPackage"
    if (somecondition)
    {
        # Do cool NuGet stuff here
    }
    # Otherwise, fall back to default TabExpansion function
    else
    {
        OriginalTabExpansion $line $lastword
    }
}
Export-ModuleMember -Function TabExpansion

We need our NuGet code to run only for two specific commands, for the first parameter. The easiest way seemed to be to check that $line is equal to one of our commands followed by the $lastword. This probably isn't the best way, but it seems to do the job for the obvious cases.

# Only run for "Get-PSPackage" or "Install-PSPackage" where we're typing the first argument
if ($line -eq "Get-PSPackage $lastword" -or $line -eq "Install-PSPackage $lastword")
{
    # ...
}

Inside this block, we need to query the OData feed for NuGet packages. Rather than hard-coding the URI, we're supposed to go through a Microsoft fwlink (https://go.microsoft.com/fwlink/?LinkID=206669), but because we can't append parameters to the end, we need to resolve this redirect. I found some code in the NuGet issue tracker written by David Ebbo to do just this. I converted it to PowerShell and added a $defaultSource variable:

# Follows redirects and returns the final URI. Used to support fwlink as package source.
function GetRedirectedUri($uri)
{
    $req = [System.Net.HttpWebRequest] [System.Net.WebRequest]::Create($uri)
    $req.AllowAutoRedirect = $FALSE; # Don't follow redirects, to save bandwidth/roundtrip
    $resp = $req.GetResponse()
    if ($resp.StatusCode -eq [System.Net.HttpStatusCode]::Redirect)
    {
        $resp.Headers["Location"]
    }
    else
    {
        $uri;
    }
}
# Set the default NuGet source
$defaultSource = GetRedirectedUri("https://go.microsoft.com/fwlink/?LinkID=206669")

With this done, we just needed to call the feed with some search parameters and parse the results. Luckily, parsing OData is pretty trivial, and with Fiddler and Visual Studio, it was easy to find the syntax for searching for packages with IDs that start with a given string :)

The final step was to pipe through Get-Unique because the OData feed returns multiple entries for packages with multiple versions (but the ID is the same):

# Construct a URI to fetch the top 30 matching packages, sorted by download count
$uri = "$($defaultSource)Packages()?`$filter=startswith(tolower(Id),'$($lastword)')&`$orderby=DownloadCount%20desc,Id&$`skip=0&`$top=30"

# Fetch the data, return as XML
$wc = New-Object Net.WebClient
$data = [xml]$wc.DownloadString($uri)

# Pipe result through Get-Unique because there could be multiple versions with the same ID
$(
    # Loop, just grabbing the ID from Properties
    foreach ($entry in $data.feed.entry)
    {
        $entry.properties.Id
    }
) | Get-Unique

And viola, Tab Expansion! Now when you type "Install-PSPackage x" or "Get-PSPackage x" you can use tab to cycle through the matching packages (sorted by download count, in an attempt to give the most likely result first).

The full module is is up on Bitbucket, but maybe with a little tidying up we can get Andrew Nurse to pull the functionality into his repository and update PSGet on NuGet.

30

April 2011

Formatting PowerShell Objects using the Razor Engine

I've been meaning to look into PowerShell for a while - it's been on an ever-growing list of things I'd like to learn more about. Recently I got around to spending some time with it and decided an interesting way to learn a little would be to create a module that allowed you to format objects using the Razor Engine.

Edit: When writing this article, I forgot to mention you need to make Powershell run .NET 4 to be able to use Razor. I did this by creating powershell.exe.config and powershell_ise.exe.config files as explained here.

The goal of the module is that you would be able to pipe some objects inand pass some template text, like this:

PS> Get-Command | Format-Razor "Command: @Model.Name, @for (int i = 0; i < 5; i++) { @i }"

Ok, so this sample isn't particularly useful, but you should get the idea. The string passed in could be read from a file, and the output could be written to a file. This will allow you to format objects using Razor, which I think it is a pretty cool templating language.

When I opened PowerShell ISE to start coding, I actually expected this to be only a line or two of code, but it turned out to be a little more complicated. Because Razor compiles to code, and you'd likely only want to do this once regardless of how many times the template would be used, it's not as simple as just passing an object and a template into Razor. First we had to create the Razor Engine:

# Create an instance of the Razor engine for C#
$language = New-Object System.Web.Razor.CSharpRazorCodeLanguage
$host = New-Object System.Web.Razor.RazorEngineHost($language)
    
# Set some default properties for the Razor-generated class
$host.DefaultBaseClass = "TemplateBase" # This is our base class (created below)
$host.DefaultNamespace = "RazorOutput"
$host.DefaultClassName = "Template"

# Add any default namespaces that will be useful to use in the templates
$host.NamespaceImports.Add("System") | Out-Null

New-Object System.Web.Razor.RazorTemplateEngine($host)

This sets up the Razor Engine with some fairly basic settings, including the name and namespace of the generated class, and also the base class. The base class is required to have a few things for Razor to be able to use it:

  • A virtual Execute() method
  • A Write(object) method, called when Razor outputs a variable/expression
  • A WiteLiteral(object) method, called when Razor outputs literal content from the template

Because it's not trivial (or certainly, I couldn't find a way) to create a class inside PowerShell, I cheated a little bit. To avoid including a binary .NET assembly, I compiled the base class from a string on the fly:

# HACK: To avoid shipping a DLL, we're going to just compile our TemplateBase class here
$baseClass = @"
using System.IO;
using System.Text;

	public abstract class TemplateBase
	{
		public StringBuilder Buffer { get; set; }
		public StringWriter Writer { get; set; }
		public dynamic $modelName { get; set; }

		public TemplateBase()
		{
			this.Buffer = new StringBuilder();
			this.Writer = new StringWriter(this.Buffer);
		}

		public abstract void Execute();

		public virtual void Write(object value)
		{
			WriteLiteral(value);
		}

		public virtual void WriteLiteral(object value)
		{
			Buffer.Append(value);
		}
	}
"@
    
# Set up the compiler params, including any references required for the compilation
$codeProvider = New-Object Microsoft.CSharp.CSharpCodeProvider
$assemblies = @(
	[System.Reflection.Assembly]::LoadWithPartialName("System.Core")
		.CodeBase.Replace("file:///", ""),
	[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.CSharp")
		.CodeBase.Replace("file:///", "")
)    
$compilerParams = New-Object System.CodeDom.Compiler.CompilerParameters(,$assemblies)
    
# Compile the template base class    
$templateBaseResults = $codeProvider.CompileAssemblyFromSource($compilerParams, $baseClass);
    
# Add the (just-generated) template base assembly to the compile parameters
$assemblies = $assemblies + $templateBaseResults.CompiledAssembly
	.CodeBase.Replace("file:///", "")
$compilerParams = New-Object System.CodeDom.Compiler.CompilerParameters(,$assemblies)
    
# Compile the Razor-generated code
$codeProvider.CompileAssemblyFromDom($compilerParams, $razorResult.GeneratedCode)

This code first compiles the base class into an assembly, and then calls Razor to generate its own code, which is then compiled with a reference to the first assembly, which contains the base template class.

Finally, we create an instance of the Razor-generated class which we'll finally use for the transformation:

# Grab the assembly that contains the Razor-generated classes
$assembly = $results.CompiledAssembly
    
# Create an instance of our Razor-generated class (this name is hard-coded above)
$type = $assembly.GetType("RazorOutput.Template")
[System.Activator]::CreateInstance($type)

To perform a transformation, all we need to do is set the Model property on the Template, and then call the Execute method:

# Set the model to the current object, using the $modelName param so the user can customise it
$template.$modelName = $model
        
# Execute the code, which writes the output to our buffer
$template.Execute()
        
# "Return" the output for this item
$template.Buffer.ToString()

And that's it, now we can format our objects using a Razor template, as shown earlier:

PS> Get-Command | Format-Razor "Command: @Model.Name, @for (int i = 0; i < 5; i++) { @i }"

The full source code is available here. Feel free to do with it as you wish. If you make any useful changes to it, please do send them over (you can send a Pull Request on Bitbucket if you like). This is my first PowerShell module, so I'm sure there's plenty that can be improved!