SaaS in our Danish Microsoft Innovation Centre
I recently took part in a SaaS proof-of-concept session in our Microsoft Innovation Centre. During a 3 weeks engagement we built a semi-cool prototype addressing some of the challenges hosters and ISVs face when entering the SaaS space. My colleague Michel Baladi will be posting a series of blog entries on the subject and I will try to stay away from boring you with stuff he already covers.
Here I will describe some of the technicalities behind a lightweight provisioning service we build during the PoC.
At the point when you need to provision a SaaS application - be it for the initial installation or adding a new application tenant to the system you will end up with a number of administrative tasks that has to be executed somehow. In the SaaS PoC the so-called application manifest files and runtime parameters, described in detail in Michel’s blog, will be merged to create a provisioning document (xml) containing install tasks. The provisioning service translates the incoming document into a .NET object model abstraction of these tasks. We considered a number of options for actually executing the corresponding provisioning and ended up with a solution involving Windows Workflow. The workflow is driven by a WCF service hosted in Internet Information Server. This is the first challenge – luckily Jeremy has very recently written an article about this that explains the details of this cocktail.
In the PoC code we have simply created a sequential workflow containing all the install tasks, but it is fairly easy to extend this to parallel activities if the provisioning involves (isolated) activities on several machines. One could also extend the install tasks with corresponding compensating activities and have the workflow calling these if something in the provisioning fails - thereby helping the administrator to rollback the changes a given faulted provisioning has made. Windows Workflow will also allow the provisioning to wait for input from another system (for example in the event of a domain name registration).
Initially I implemented some of the install tasks (copy files, add DNS A record, create web site etc.) directly in workflow activities but got (partly) convinced to have a look at PowerShell Cmdlets.
It should be a well known fact by now that Microsoft is betting on their latest administrative console shell (PowerShell aka Monad - and no, not the lesser known PowerShell for the X11 Windows System). At first I was (and partly am) very skeptic about the fact that you can do so much damage so easily, but I am starting to accepts this … and I guess balancing admin power and responsibility goes for many things in our industry. I must admit after playing a bit with PowerShell that it is a major leap forward from .cmd and .bat files I’ve written in the past. With PowerShell I can do many of the things I would normally build small utility programs for directly in a (.ps1) script file which is very nice. That said I would still prefer a dedicated program for a given administrative task with built-in logic for handling my misunderstanding of infrastructure “business rules”. Cmdlets does this… they are actually just mini-programs written in your managed language of choice. I can’t help myself at this point to remind you of all the very cool features our new C++/CLI language can do for you in this space – you will have direct access from your C++/CLI code to all the unmanaged Windows APIs still existing in our operating systems that you would have to P/Invoke from a language such as C# or VB.NET.
Long story short - we chose to use PowerShell Cmdlets to encapsulate common provisioning tasks such as: Creating IIS Web sites, Adding DNS Zones and Records, Altering config files etc. My colleague Mario Briana did a lot of the legwork here and built a number of provisioning Cmdlets. We extended all install tasks with the method: string ToPowerShellCommand();
For example:
public override string ToPowerShellCommand()
{
StringBuilder sb = new StringBuilder();
sb.Append("Add-XmlSection ");
sb.AppendFormat("-Filename \"\\\\{0}\\{1}\" ", server, filename);
sb.AppendFormat("-XpathQuery \"{0}\" ", xpathQuery);
sb.AppendFormat("-Section {0} ", _val);
sb.Append(Environment.NewLine);
return sb.ToString();
}
With Add-XmlSection being one of our homegrown PowerShell Cmdlets.
In this way all tasks extracted from a provisioning document can easily be transformed into a PowerShell command script:
List<InstallTask> tasks = Parser.ParseProvisionDocument(doc);
StringBuilder sbScript = new StringBuilder();
foreach (InstallTask t in tasks)
{
sbScript.AppendLine(t.ToPowerShellCommand());
}
(The class InstallTask above is the common base class for all dedicated InstallTasks (f.ex. SetXmlElementInstallTask))
The resulting script from this was used initially to test our concept from the PowerShell command line. However nice the new command line is we needed the provisioning to be run from a service without having an administrator executing this in a PowerShell. For this purpose you should use a so-called runspace populated with your custom snap-ins demonstrated below:
public Runspace InitRunspace(string provisioningSnapInName) {
RunspaceConfiguration config = RunspaceConfiguration.Create();
if (!string.IsNullOrEmpty(provisioningSnapInName))
{
PSSnapInException warning;
config.AddPSSnapIn(provisioningSnapInName, out warning);
if (warning != null)
return null;
}
return RunspaceFactory.CreateRunspace(config);
}
Given a runspace you can create a pipeline and execute this as:
Collection<PSObject> output = new Collection<PSObject>();
Pipeline pipeline = _runspace.CreatePipeline();
using (pipeline)
{
pipeline.Commands.AddScript(_script);
output = pipeline.Invoke();
}
With _script simply being a string representation of the PowerShell command you wish to execute – for example: “Copy-Item –Path C:\test.xml –Destination C:\test2.xml”. The creation of a runspace is a relatively expensive task (I was told – haven’t checked). In our case with the provisioning tasks being executed in a Windows Workflow it seems pretty obvious to store one common Runspace in the workflow as a service:
WorkflowInstance instance = _runtime.CreateWorkflow(…);
instance.WorkflowRuntime.AddService(runspace);
Thereby making it available in all activities in the workflow:
ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
Runspace _runspace = executionContext.GetService<Runspace>();
…
}
The code shown above was encapsulated in a Custom Workflow Activity and the workflow built up (simply adding powershell activities to a top level sequential workflow) from the list of install tasks as discussed above. There are many more details to this but I better stop at this point and publish instead of having this document hanging around my desktop. I would like to thank the people participating in the PoC for another good experience in the Danish Microsoft Innovation Center.