PSDefaultVariablizer – I didn’t know what else to name it!

public class PSDefaultVariablizer<T> where T: class
{
    string variableName;
    T innerValue;
    PSCmdlet cmdlet;
 
    public PSDefaultVariablizer(string _variableName, PSCmdlet _cmdlet)
        : this(_variableName, null, _cmdlet) { }
 
    public PSDefaultVariablizer(string _variableName, T _innerValue, PSCmdlet _cmdlet)
    {
        variableName = _variableName;
        innerValue = _innerValue;
        cmdlet = _cmdlet;
    }
 
    public T Value
    {
        get
        {
            if(innerValue != null)
            {
                return innerValue;
            }
            else
            {
                return (T)cmdlet.SessionState.PSVariable.GetValue(variableName, null);
            }
        }
        set
        {
            innerValue = value;
        }
    }
}

Here is the use case for the class. You have a parameter that a user can input, otherwise it attempts to use a value of a variable in the same scope (think how $ErrorActionPreference works). This class allows you to very simply reuse that functionality.

[Cmdlet("Some", "Command")]
public class SomeCommand : PSCmdlet
{
    private PSDefaultVariablizer<string> someParam;
 
    public SomeCommand ()
    {
        someParam = new PSDefaultVariablizer<string>("SomeParam", this);
    }
 
    [Parameter()]
    public string SomeParam
    {
        get
        {
            return someParam.Value;
        }
        set
        {
            someParam.Value = value;
        }
    }
}

And you would use it from Powershell with the following.

Some-Command -SomeParam 'hello'
#or
$SomeParam = 'hello'
Some-Command

Pretty simple really. I just posted it because I thought the name was hilarious, but I didn’t really know what else to call it.

Let me know if anyone knows of an easier way to this. Unfortunately, there aren’t too many people I can talk to about SnapIn development :)

Scheduled Tasks in Powershell, Alpha

Scheduled tasks don’t have any good way to be edited in Powershell, at least from what I have seen. The other day I set out to write some scripts to help me out. This isn’t really cleaned up or final (aka I haven’t figured out the best error checking yet and it could use a decent refactoring), but I figured I would go ahead and throw them up here to get some feedback.

 
function get-scheduledtask($server=$env:COMPUTERNAME)
{
	$tempFile = $env:TEMP + "\scheduledtaskstemp.csv"
	schtasks /query /S $server /FO csv /V | where { $_.length -gt 0 } | Set-Content $tempFile
	Import-Csv $tempFile
	Remove-Item $tempFile
}
 
function remove-scheduledtask($taskname, $server=$env:COMPUTERNAME, [switch]$verbose, [switch]$whatif=$variable:whatifpreference)
{
	if(-not $taskname) { throw "Must provide taskname" }
	$command = "schtasks /DELETE /TN '$taskName'"
	if($server) { $command += " /S '$server' " }
 
	if($verbose -or $whatif) { $command }
	if(-not $whatif) { Invoke-Expression $command }
}
 
function run-scheduledtask($taskname, $server=$env:COMPUTERNAME, [switch]$verbose, [switch]$whatif=$variable:whatifpreference){
	if(-not $taskname) { throw "Must provide taskname" }
	$command = "schtasks /RUN /TN '$taskname'"
 
	if($server) { $command += " /S '$server' " }
 
	if($verbose -or $whatif) { $command }
	if(-not $whatif) { Invoke-Expression $command }
}
 
function add-scheduledtask {
	param
	(
		[string] $user, 
		[string] $password, 
		[string] $schedule,
		[string] $runUser,
		[string] $runPassword,
		$modifier, 
		$days, 
		$idleTime, 
		$taskName, 
		$taskRun, 
		$startTime, 
		$months, 
		$startDate, 
		$endDate, 
		$server=$env:COMPUTERNAME,
		[switch]$verbose,
		[switch]$whatif = $variable:whatifpreference
	)	
 
	if(-not $taskName) { throw "Must Provide a taskName" }
	if(-not $taskRun) { throw "Must provide a taskRun" }
	if(-not $schedule) { throw "Must provide a schedule" }
	if(-not $runPassword) { throw "Must provide a runPassword" }
	if(-not $runUser) { throw "Must provide a runUser"}
 
	$command = "schtasks /CREATE /TN '$taskname' /TR '$taskRun' /SC '$schedule' /RU '$runUser' /RP '$runPassword' "
 
	($user,"U"),
	($password,"P"),
	($modifer,"MO"),
	($days,"D"),
	($months,"M"),
	($idleTime,"I"),
	($startTime,"ST"),
	($server,"S"),
	($startDate,"SD"),
	($endDate,"ED")| 
		%{
			if($_[0]) { $command += " /$($_[1]) '$($_[0])' " }
		}
 
	if($verbose -or $whatif) { $command }
	if(-not $whatif) { Invoke-Expression $command }
}

Powershell and Grep

I am running Powershell V2 and needed to look through some log files from a bunch of different servers. Powershell V2 and this article showed me the light.

gci -recurse | gc | select-string -context 50 "some string to match" > results.txt

The context parameter is a Powershell V2 addition that helps give lines above and below the matched string. Select-String is slowly becoming the grep I need in Windows.

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.