The windows PowerShell engine can be hosted in the System.Management.Automation.PowerShell
class. Using this class you can create, execute and manage commands in a Runspace. We’ll use these features of
the PowerShell class to load and execute our Modules and interact with the PowerShell
engine whenever needed in our unit test framework. While creating the
PowerShell host for our test framework, it’s good to define the Runspace that is responsible for the
operating environment for command pipelines. In our framework I preferred to
use the constrained runspace which will allow us to restrict the programming
elements that can be applied by the user.
We’ll later use this ability of constrained runspaces to
simulate a Stub behavior in our test framework. To restrict the availability of
aliases, applications, cmdlets, functions and scripts, we’ll create an empty InitialSessionState and use that for
creating a runspace. Later we’ll use the AddPSModule, AddCommand, AddPSSnapin
methods to include the required functionality in our runspace in a text
context.
InitialSessionState has
three different methods to create a container that holds commands
- Create - Creates an empty container. No commands are added to this container.
- CreateDefault - Creates a session state that includes all of the built-in Windows PowerShell commands on the machine. When using this API, all the built-in PowerShell commands are loaded as snapins.
- CreateDefault2 - Creates a session state that includes only the minimal set of commands needed to host Windows PowerShell. When using this API, only one snapin – Microsoft.PowerShell.Core - is loaded.
We’ll use the CreateDefault2 overload method to create the
InitialSesionState instance.
private InitialSessionState CreateSessionState(string path)
{
var state = InitialSessionState.CreateDefault2();
if (!String.IsNullOrEmpty(path))
{
state.ImportPSModulesFromPath(path);
}
return state;
}
In the CreateSessionState method, we use the Path property of
the PSModuleAttribute created in the part 1 of this series to load all modules
from the path provide to the InitilSessionState.
Once we have the InitialSessionState, we’ll use this container
to create the Runspace and then the PowerShell host
_runspace = RunspaceFactory.CreateRunspace(state);
_runspace.Open();
_shell = PowerShell.Create();
_shell.Runspace
= _runspace;
We’ll use this PowerShell instance to execute the commands
needed from the module. In the framework, I have my execute method created as
public TModule Execute(string method)
{
if (_shell == null)
{
throw new ArgumentException("The PowerShell host should be setup before invoking
the methods");
}
_shell.AddCommand(_moduleInfo.Name + @"\" + method);
var methodProperties = typeof(TModule).GetProperties()
.Where(prop =>
prop.IsDefined(typeof(PsModuleFunctionAttribute), false)).ToList();
var property = methodProperties.First(p =>
p.GetCustomAttribute<PsModuleFunctionAttribute>().Name == method);
var commandInfo = property.GetValue(_module) as PsCommandInfo;
var parameters = commandInfo.Parameters;
if (parameters != null)
{
_shell.AddParameters(parameters);
}
var results = _shell.Invoke();
commandInfo.Result = results;
property.SetValue(_module,
commandInfo);
DisposeContext();
return _module;
}
As you can see from the highlighted code, we add the commands
and the parameters to the shell using reflection from the metadata defined in
the attributes to invoke the shell. The results are set back as property values
to the ModuleObject and returned back to the test to assert the conditions.
A simple test code using the framework can be created like
var psHost = new PowerShellHost<XModule>();
var actual = psHost
.Execute("Get-Greetings");
var result = actual.GetGreetings.Result.Select(psObject =>
psObject.BaseObject).OfType<string>().First();
Assert.AreEqual<string>(result, "Hello from VSTest");
Next in the series, we’ll see how to make use of our
framework to stub methods/ cmdlets in the execution pipeline.
No comments:
Post a Comment