Using Pipeline.Input for Powershell testing

Last week, I posted about how to unit test powershell, which I have been working with a little bit over the weekend. One thing I quickly realized was testing interaction with the pipeline is a must for Powershell. It’s pretty easy to do.

Let’s start with the Get-Service cmdlet that ships with Powershell. If you load up reflector, you will see the follow class definition.

[Cmdlet("Get", "Service", DefaultParameterSetName="Default")]
public sealed class GetServiceCommand : MultipleServiceCommandBase
{
    // Methods
    public GetServiceCommand();
    protected override void ProcessRecord();
 
    // Properties
    [Parameter(Position=0, 
          ParameterSetName="Default", 
          ValueFromPipelineByPropertyName=true, 
          ValueFromPipeline=true),
     Alias(new string[] { "ServiceName" })]
    public string[] Name { get; set; }
}

Remembering the base test fixture from the previous post on TDD in Powershell, I can quickly write up a few tests that test a few different use cases of the get-service cmdlet with

 
[Test]
public void Get_Service_with_single_string_in_pipeline() 
{
    // "MySQL" | get-service
 
    var name = "MySQL";
 
    var pipeline = Runspace.CreatePipeline();
 
    pipeline.Input.Write(name);
    pipeline.Commands.Add("get-service");
 
    var result = pipeline.Invoke();
 
    AssertThatPipelineResultIsService(result, name);
}
 
[Test]
public void Get_Service_with_single_object_in_pipeline_using_Name_property()
{
    //New-Object PSObject | Add-Member NoteProperty Name "MySQL" -PassThru | get-service
 
    var name = "MySQL";
 
    var pipeline = Runspace.CreatePipeline();
 
    pipeline.Input.Write(new { Name = name });
    pipeline.Commands.Add("get-service");
 
    var result = pipeline.Invoke();
 
    AssertThatPipelineResultIsService(result, name);
}
 
[Test]
public void Get_Service_with_single_object_in_pipeline_using_ServiceName_property()
{
    //New-Object PSObject | Add-Member NoteProperty ServiceName "MySQL" -PassThru | get-service
 
    var name = "MySQL";
 
    var pipeline = Runspace.CreatePipeline();
 
    pipeline.Input.Write(new { ServiceName = name });
    pipeline.Commands.Add("get-service");
 
    var result = pipeline.Invoke();
 
    AssertThatPipelineResultIsService(result, name);
}
 
private void AssertThatPipelineResultIsService(Collection<PSObject> result, string name)
{
    Assert.That(result.Count == 1);
    Assert.That(result[0].BaseObject is ServiceController);
    Assert.That((result[0].BaseObject as ServiceController).ServiceName == name);
}

By using pipeline.Input.Write(), I can write to the pipeline before invoking commands. Really easy to do, and super useful, because each Powershell cmdlet can probably be called a bunch of different ways.

Where can you use it? Snap in development or testing your own Powershell scripts. Stop guessing how the pipeline is going to work and test it.

TDD with Powershell, from the client perspective

Is it possible? Yes, and it is actually relatively easy. The hardest part can be figuring out how to mock/stub out certain functionality so your testing small units as opposed to full acceptance testing. The line can get blurry, but I don’t really care. Testing is half the battle.

But to be clear, the focus of my testing is to test how a snap-in is working. It sounds like an integration test, and maybe it is, but there is no reason you couldn’t test any powershell script using some of the steps outlined here. Just be flexible and creative.

Start by creating a nice base test fixture.

public abstract class PowershellTestFixture
{
    protected Runspace Runspace { get; set; }
 
    public void CreateRunspace()
    {
        Runspace = RunspaceFactory.CreateRunspace(CreateConfiguration());
        Runspace.Open();           
    }
 
    public void CloseRunspace()
    {
        Runspace.Close();
        Runspace.Dispose();
    }
 
    private RunspaceConfiguration CreateConfiguration()
    {
        var config = RunspaceConfiguration.Create();
 
        PSSnapInException warning;
        config.AddPSSnapIn("SomeCustomSnapIn", out warning);
 
        return config;
    }
 
    protected Collection<PSObject> ExecutePowershell(string script)
    {
        return Runspace.CreatePipeline(script).Invoke();
    }
}

In this base class, I just created some nice helper methods, and one method that samples how to add a snap-in to the runspace.

Now, just write a test, and add your assertions. And it will work!

[TestFixture]
public class TestSomethingStupid : PowershellTestFixture
{
    [SetUp]
    public void Setup()
    {
        CreateRunspace();
    }
 
    [TearDown]
    public void TearDown()
    {
        CloseRunspace();
    }
 
    [Test]
    public void GetServiceWhereNameIsMySql()
    {
        string cmd = @"
get-service | where { $_.name -eq 'MySQL' }
";
        var result = ExecutePowershell(cmd)
            .Select(o => o.BaseObject)
            .Cast<System.ServiceProcess.ServiceController>()
            .First();
 
        Assert.AreEqual(result.ServiceName, "MySQL");            
    }
}

Obviously this test is stupid. But that’s not the point. In a very few lines, I have tested the output of some powershell. Pretty cool if you ask me, especially if your coding a snap in. If you aren’t automating the tests, how else can you verify it works except to recompile, install, open powershell, and type some code? Very slow and painful!

It is important to note, there are other ways to test snap-in’s. What I really wanted to see is how my cmdlet’s looked from the powershell perspective, not the C# perspective.

Selling Cloud Computing with Customer Stories

Today I gave a talk about Cloud Computing, and was posed with an interesting question.

How can we sell cloud computing to the non-technical decision makers?

I wasn’t really prepared for the question, because I was trying to sell it to technical people. But it is a very valid question, because the IT department of many businesses must go through others to get decisions approved.

The first thing that came to mind wasn’t cost, or ease of use, it was who else is using it? When looking at outsourcing to another company, one of the most important things to do is understand how others view the company. For the same reason Amazon has user reviews of products, you need to look at reviews of cloud service providers. Who uses them and likes them? Who dislikes them and why?

Most cloud companies have a list of customers using their services. Understanding how others are using the cloud is really the easiest way to understand how you can use the cloud. Forget all the technical details, they don’t matter to non-technical people.

Some great examples are Mailtrust, AWS, Mosso, 3tera, Salesforce, Rackspace, and the list goes on…

Everyone nodded their head at my answer. Hopefully it was a good one.

Any other ideas on how to sell cloud computing to non-technical people?

Kill SQL Connections by Login

Ever get this error message?

Exclusive access could not be obtained because the database is in use.
RESTORE DATABASE is terminating abnormally.

You can’t do a restore will connections to that database still exist!

If you have jobs that do restores of databases for one reason or another, I bet you have seen this message. I have a job to do a nightly restore of a backup, but some users just never disconnect even though I continually shake my finger. So what do I do? I kill all the processes from problem users before starting the restore.

 
--Create the cursor
DECLARE mycursor cursor
FOR
 
-- Query for processes by login and database
SELECT spid, Loginame
FROM master..sysProcesses
WHERE Loginame='someuser' AND dbid=db_id('somedb')
 
 
OPEN mycursor
 
DECLARE @spid INT, @loginame VARCHAR(255), @cmd VARCHAR(255)
 
-- Loop through the cursor, killing each process
Fetch NEXT FROM MYCursor INTO @spid, @loginame
While (@@FETCH_STATUS <> -1)
BEGIN
	-- I don't really know why this is necasary, but it is.
	SELECT @cmd = 'kill ' + CAST(@spid AS VARCHAR(5))
	EXEC(@cmd)
 
	Fetch NEXT FROM MYCursor INTO @spid, @loginame
END
 
close mycursor
deallocate mycursor
GO

I am no TSQL expert, so kudos to this article for helping me figure out why kill @spid wasn’t working.

Oh you guys…

Stop trying to hack my blog with SQL injection attacks. If I wasn’t so tired, I would block your IP, but I am confident enough to wait till tomorrow.

sql-injection-attempt

PS – Why I am even nice enough to blur the IP address I don’t even know.