Daniel Cazzulino's Blog :

PowerShell (RSS)

How to use C# typeof in Powershell

Have you ever missed C#'s "typeof" while working with PowerShell?

PS C:\> [System.Type]::GetType("System.Enum") 

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Enum                                     System.ValueType

PS C:\> typeof System.Enum

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Enum                                     System.ValueType

I know I do. So I created the following snap-in:

using System.Globalization;
using System.Management.Automation;

namespace Clarius.PowerShell
{
    [Cmdlet(VerbsCommon.Get, "Type")]
    public class GetTypeCommand : Cmdlet
    {
        private string typeName;

        [Parameter(Mandatory=true, Position=0, HelpMessageResourceId="Parameter_TypeName")]
        public string TypeName
        {
            get { return typeName; }
            set { typeName = value; }
        }

        protected override void ProcessRecord()
        {
            string name = typeName;
            if (name.StartsWith("[")) name = name.Substring(1);
            if (name.EndsWith("]")) name = name.Substring(0, name.Length - 1);

            WriteObject(new TypeNameConverter().ConvertFrom(name, typeof(Type), CultureInfo.CurrentCulture, true));
        }
    }
}

And after registering your snap-in you can add the following alias on your profile:

set-alias typeof Get-Type

And now you can use your beloved "typeof" passing just the type name.

posted Tuesday, August 21, 2007 4:13 PM by kzu with 0 Comments

PowerShell with TFS: how to perform batch-updates to WorkItems

In this post, I'll show how you can use Powershell to perform one of the most annoying and (currently impossible) tasks in an iteration planning using TFS: batch update of all non-completed items so that they are moved to the next iteration.

The end result (which I'm using very effectively weekly :)) will be to be able to issue the following commands:

PS C:\> $tfs = Connect-Tfs tfs
PS C:\> $tfs.WorkItems.FindAll("[System.TeamProject] = 'MyProject' and [System.State] != 'Closed' and [System.IterationPath] = 'MyProject\Iteration 1'") | %{ $_.Open; $_.Fields["System.IterationPath"].Value = "MyProject\Iteration 2"; $_.Save(); write-host Updated $_.Title; } | out-null

 

and have all work items in "MyProject" from iteration 1 that haven't been closed, moved to iteration 2.

This even works with CodePlex-hosted projects :)

So, the first thing is to get connected to TFS. For this, I'm using the Microsoft.TeamFoundation.Client.dll API. The only farily undocumented caveat is that you need to pass a UICredentialsProvider when connecting:

				using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Management.Automation;
using Microsoft.TeamFoundation.Client;

namespace Clarius.PowerShell.TeamFoundation
{
    [Cmdlet(VerbsCommunications.Connect, "Tfs")]
    publicclassConnectCmdlet : Cmdlet
    {
        privatestring server;

        [Parameter(Mandatory = true, Position=0, HelpMessageResourceId="Parameter_ServerName")]
        publicstring Server
        {    
            get { return server; }
            set { server = value; }
        }

        protectedoverridevoid ProcessRecord()
        {
            TeamFoundationServer tfs = newTeamFoundationServer(server, newUICredentialsProvider());
            tfs.EnsureAuthenticated();

            WriteObject(tfs);
            base.ProcessRecord();
        }
    }
}

With that in place, and after registering your snap-in, you can issue the first command and get a full TeamFoundationServer object back. Now, that object's API is not very script-friendly. You have to use GetService to achieve anything. In order to expose the WorkItems service in a script-friendly way, you can leverage PowerShell's type extension mechanism:

				<?
				xml
				
				
				version
				="1.0"encoding="utf-8" ?>
<Types>
  <Type>
     <Name>Microsoft.TeamFoundation.Client.TeamFoundationServer</Name>
     <Members>
        <ScriptProperty>
          <Name>WorkItems</Name>
          <GetScriptBlock>
             [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client") | out-null
             [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.WorkItemTracking.Client") | out-null
             [Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore]$this.GetService([type]"Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore")
          </GetScriptBlock>
        </ScriptProperty>
     </Members>
  </Type>
</Types>

With that extension in place, we can now access the service just using $tfs.WorkItems. Note that I'm loading the necessary assemblies before invoking the APIs, so that the relevant types are available to PS. But of course, the really interesting stuff is getting the nice FindAll() .NET 2.0-ish member working:

PS C:\> $tfs.WorkItems.FindAll("[System.TeamProject] = 'MyProject'")

This time it's a type extension on the WorkItemStore type:

				  <
				Type
				>
     <
				Name
				>Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore</Name>
     <Members>
        <ScriptMethod>
          <Name>FindAll</Name>
          <Script>
             $private:fields = @();
             foreach ($field in $this.FieldDefinitions)
             {
             $fields += ("[" + $field.Name + "]");
             }
             $private:all = [string]::Join(",", $fields);

             if ($args[0] -is [ScriptBlock])
             {
             $private:predicate = $args[0];
             $private:result = $this.Query("SELECT " + $all + " FROM WorkItems");
             foreach($item in $result)
             {
             if ([bool](&amp;$predicate $item))
             {
             write-output $item;
             }
             }
             }
             elseif ($args[0] -is [string])
             {
             $this.Query("SELECT " + $all + " FROM WorkItems WHERE " + $args[0]);
             }
          </Script>
        </ScriptMethod>
     </Members>
  </Type>

Note that because of a pretty annoying "bug" (will be fixed in Orcas + 1, aka Rosario), you can't actually issue a SELECT * query using the WorkItem Query Language (WIQL) and instead you need to explicitly pass all fields you're interested in retrieving. Because you may want to later update any of the fields, I'm just building the entire list of all fields and passing that to the service.

Note that you can either pass a predicate to be evaluated for each item, or a string with the WHERE clause, which is what I did at the beginning.

With all that in place, all that's left is to pipe the items resulting from the query and perform any operation you like on them!

Enjoy.

posted Tuesday, August 21, 2007 4:38 PM by kzu with 0 Comments

PowerShell needs an XML namespace before it's too late
PowerShell exposes some pretty cool features via XML files, such as custom formatting and type extensions.

The only problem I see right now is that those XML files have no namespace whatesoever. That may sound useless to you, but it's the very reason why you don't get ANY intellisense/validation support for those files from withing Visual Studio.

Vote for the suggestion before it's too late (if it's not already :((()

posted Friday, November 03, 2006 5:05 AM by kzu with 0 Comments

PowerShell: how to unit test your cmdlet

If you miss VS, intellisense, TD.NET, etc., you might want to try extending PowerShell with custom cmdlets, which are .NET classes deriving from Cmdlet. They allow you to extend PowerShell while still programming in your favorite language.

Read Pablo Galiano's post for a step-by-step introduction to Cmdlets.

I'm hooked to PowerShell. It's been really fun to learn, and I'm loving it. 

I'm also hooked to Test Driven Design (that's what TDD should mean, IMO), so I naturally looked for a way to develop my cmdlets in a TDD way. Turns out that it's fairly easy. For this example, I will show a simple cmd-let that should load an assembly in a flexible way (by file name, full name or partial name).

First, create your unit test (ha! you thought I was going to create the cmdlet first?? :p):

[TestMethod]

public void ShouldCreateCmdLet()

{

    LoadCommand cmd = newLoadCommand();

 

    Assert.IsTrue(cmd isCmdlet);

}

 

In order to get that test to compile, create your class deriving from CmdLet:

 

[Cmdlet("Load" , "Item", DefaultParameterSetName="Item")]

public class LoadCommand : Cmdlet

{

    protectedoverridevoid ProcessRecord()

    {

        base.ProcessRecord();

    }

}

 

For comprehensive guidelines on CmdLet development, see the MSDN documentation. 

Next, create the test that passes input to the cmdlet. Here's where the real cmdlet testing occurs:

 

[TestMethod]

public void ShouldLoadAssemblyWithFileName()

{

    string asmFile = this.GetType().Module.FullyQualifiedName;

 

    LoadCommand cmd = newLoadCommand();

    cmd.Item = asmFile;

 

    IEnumerator result = cmd.Invoke().GetEnumerator();

 

    Assert.IsTrue(result.MoveNext());

    Assert.IsTrue(result.Current isAssembly);

 

    Assert.AreEqual(this.GetType().Assembly.FullName, ((Assembly)result.Current).FullName);

}

 

Note that there's no way to call ProcessRecord directly. The way to run the cmdlet is to call Invoke, and getting an enumerator from it. Remember that when placed in the pipeline, the cmdlet will be called once for each input in the pipeline. Let's now implement the cmdlet to make the test pass (and compile!):

 

[Cmdlet("Load" , "Item", DefaultParameterSetName="Item")]

public class LoadCommand : Cmdlet

{

    private string assembly;

 

    [Parameter(Mandatory=true, ValueFromPipeline=true, ParameterSetName="Item", Position=0, HelpMessageResourceId="LoadCmdlet_Item")]

    public string Assembly

    {

        get { return assembly; }

        set { assembly = value; }

    }

 

    protectedoverridevoid ProcessRecord()

    {

        base.ProcessRecord();

 

        if (File.Exists(assembly))

        {

            WriteObject(Assembly.LoadFrom(fileName));

            return;

        }

    }

}

 

Note that in order to return output from your cmdlet, you call WriteObject. It's interesting to debug the test we wrote. You will notice that the call to Invoke doesn't actually execute the cmdlet. Instead, moving the enumerator does. This is how the pipeline achieves lazy evaluation of each "step" and continues executing with the following commands. Very cool.

And that's pretty much all there is to it. Now you can start adding tests and the corresponding features to your cmdlet, with the amazing piece of mind that comes from having a unit test that says that it actually works ;). Needless to say, this test-code-run is much faster than testing the cmdlet directly in PowerShell (you need to constantly exit PS and re-enter, re-add your snapin, etc., otherwise the output assembly gets locked).  

posted Thursday, October 26, 2006 9:02 AM by kzu with 1 Comments

PowerShell: vastly improved tab expansion/completion (or do you still miss VS intellisense?)

I'm an intellisense-addict. I hate doing work that my computer can do for me, and typing long namespace and class names certainly fits that category.

[If you know all about PowerShell and just want to try it really quick, go get the straight dope now ;)]

Out of the box, PowerShell (PS) does fairly minimal tab completion: simple member expansion on variables, variable name expansion and parameter completion on commands. The tab expansion in PS is implemented in a script function named TabExpansion. Turns out that functions are exposed via a custom PS provider (like the file system provider, the registry provider, etc.), meaning you can "cd" into functions and do a dir|gci|get-childitem on it

PS C:\> Get-PSProvider

Name Capabilities
---- ------------
Alias ShouldProcess
Environment ShouldProcess
FileSystem Filter, ShouldProcess
Function ShouldProcess
Registry ShouldProcess
Variable ShouldProcess
Certificate ShouldProcess


PS C:\> cd Function:
PS Function:\> gci

CommandType Name
----------- ----
Function prompt
Function TabExpansion
Function Clear-Host
Function more
Function help
Function man
...


Now, being exposed as items over a provider, means that you can also call cat|gc|get-content on it, and see the actual function implementation:

PS Function:\> gc TabExpansion


# This is the default function to use for tab expansion. It handles simple
# member expansion on variables, variable name expansion and parameter completion
# on commands. It doesn't understand strings so strings containing ; | ( or { may
# cause expansion to fail.

param($line, $lastWord)


switch -regex ($lastWord)
{
# Handle property and method expansion...
...

 
You can also do gc function:tabexpantion, directly. That's pretty amazing. Read more about it at the Windows PowerShell blog.

Being just a function, it can be overridden in your profile, so you can grab the default expansion implementation, and extend it in any way you want. I found a coupleversions of a pretty cool extended tab expansion that did almost what I wanted: full expansion on namespace, type name and members. Looks like the latest version is hosted in CodePlex. With that extended function added to my profile, I can do (first the line ending with the [TAB] keypress, next what's completed by the tab expansion):

PS C:\> [S[TAB]
PS C:\> [System
PS C:\> [System.Ref[TAB]
PS C:\> [System.Reflection
PS C:\> [System.Reflection.As[TAB]
PS C:\> [System.Reflection.Assembly]
PS C:\> [System.Reflection.Assembly]::LoadW[TAB]
PS C:\> [System.Reflection.Assembly]::LoadWithPartialName(
PS C:\> [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Build.Framework")

Bold is what I actually had to type. So I entered a 79 characters statement only typing 43 characters! And of those, 28 were the actual method parameter! So, if I don't count the parameter, I typed 1/3 of what I would have had to before. Amazing boost in speed :).

There were a couple annoyances with the implementation, though: System and Microsoft were hardcoded as the only top level namespaces recognized, and it didn't refresh the cache when I loaded an assembly via Assembly.LoadX. Those were significant drawbacks that I set to fix for myself. In the course of fixing it, I ended up re-implementing the caching in a completely different way, that makes for a much faster in-memory cache building process, as well as an optimized storage. Now I can load an assembly on the prompt, and have immediate intellisense on the types from that assembly :)

Get the improved tab expansion from the PowerShell Wiki.

posted Thursday, October 26, 2006 7:29 AM by kzu with 3 Comments