IIS Snapin extensibility. Part 5.

Super types from assemblies

In my previous post I explained how to extend namespace objects using XPath queries and PowerShell script properties. We also discussed performance implications from these extension techniques. Today I will show you how to extend existing namespace types with minimal effect on performance. We will inherit new managed type from the existing one and will access all additional data from compiled code. 

All types used in IIS provider are implemented in the assembly Microsoft.IIs.PowerShell.Framework. When you install provider, this assembly is getting installed into GAC. We could extract it from there and use for reference. To find the assembly, you could simply run ‘dir’:

dir c:\windows\assembly\Microsoft.IIs.PowerShell.Framework.dll /s
 Volume in drive C has no label.
 Volume Serial Number is 7840-32FF

Directory of c:\windows\assembly\GAC_MSIL\Microsoft.IIS.PowerShell.Framework\7.5.0.0__31bf3856ad364e35

04/24/2009  12:20 AM           196,608 Microsoft.IIS.PowerShell.Framework.dll
               1 File(s)        196,608 bytes

Now you could copy DLL from this folder into your working folder, I will use e:\dev\PowerShell\SuperSite.

copy c:\windows\assembly\GAC_MSIL\Microsoft.IIS.PowerShell.Framework\7.5.0.0__31bf3856ad364e35\Microsoft.IIS.PowerShell.Framework.dll e:\dev\PowerShell\SuperSite

Let's redefine Site node and add perf counter that will show total number of GET requests that were processed for this site. To add new property to the type we will need to override method LoadProperties and add constructors for node and factory types.

using System;
using System.Collections.Generic;
using Microsoft.IIs.PowerShell.Framework;
using System.Diagnostics;

namespace SuperSite
{
public class SuperSite : Site
{
    public SuperSite(
        INamespaceNode parent,
        string itemXPath,
        TypeInfo data,
        TypeManager tm
        )
        : base(parent, itemXPath, data, tm)
    {
    }

    protected override void LoadProperties(
        Dictionary<string, object> collection
        )
    {
        base.LoadProperties(collection);
        // Add properties that we want to have on super site
        PerformanceCounter totalGets = new PerformanceCounter();
        totalGets.CategoryName = "Web Service";
        totalGets.CounterName = "Total Get Requests";
        totalGets.InstanceName = this.Name;
        totalGets.MachineName = ".";
        totalGets.ReadOnly = true;
        float value = totalGets.NextValue();
        collection.Add("TotalGetRequests", (int)value);
    }
}

public class SuperSiteFactory : SiteFactory
{
    public SuperSiteFactory()
    : base()
    {
    }
}
}

That's all we need. When the code in parent class will convert data to PowerShell format, it will convert our new property too. Next we have to update definition for site node in the namespace layout, now it should refer to our types.

<?xml version="1.0" encoding="utf-8" ?>
<navigationTypes>
  <site>
    <type>SuperSite.SuperSite</type>
    <factoryType>SuperSite.SuperSiteFactory</factoryType>
    <assembly>SuperSite, Version=0.0.0.0, Culture=neutral, PublicKeyToken=1a5b963c6f0fbeab, processorArchitecture=MSIL</assembly>
    <instanceXPath>/system.applicationHost/sites/site[@name="%name%"]</instanceXPath>
    <filePath>MACHINE/WEBROOT/APPHOST</filePath>
    <configurationPath>%filePath%/%name%</configurationPath>
    <parents>
      <parentInfo>
        <parentType>Sites</parentType>
        <xPath>%instanceXPath%/site</xPath>
        <filePath>MACHINE/WEBROOT/APPHOST</filePath>
      </parentInfo>
    </parents>
  </site>
</navigationTypes>

You will need to place your assembly to the GAC and use fully qualified assembly name in this file. Besides type and assembly names other definitions are the same as for original node.

If you are working in Visual Studio, you could setup your project to produce signed assembly with the version you want. Otherwise you will need SDK utilities sn.exe and gacutil.exe to produce signed assembly and to place it into the GAC. To produce key pair for signing, use command

sn -k test.snk

Assuming that you run command from the same folder, where you copied reference assembly file, key pair file and that source code for types is in the file SuperSite.cs, here is the command to build your assembly:

e:\dev\powershell\SuperSite>csc /r:Microsoft.IIS.PowerShell.Framework.dll /target:library /out:SuperSite.dll /keyfile:test.snk SuperSite.cs

Now install result into the GAC:

e:\dev\powershell\SuperSite>gacutil /i SuperSite.dll

Layout file for the "super site" (SuperSite.namespace.xml) should be copied to the same folder, where you have installed the original iisprovider.namespace.xml. If you installed IIS provider released out-of-band, it is "%ProgramFiles%\IIS\PowerShellSnapin", otherwise it is in %PSModulePath%\WebAdministration.

Now, when you start PowerShell and import module (or add snap-in) WebAdministration, your new layout file will be loaded and provider will use your types when accessing sites. Each site node will have new property TotalGetRequests. If you run command 'dir iis:\sites' you will see that suddenly all output is unformatted. Why is this? Let's figure this out. Format templates are defined by entries in the TypeNames collection, which is added to PSObject, produced from our data object.

PS E:\dev\powershell\Bin #> $s = gi iis:\sites\moin
PS E:\dev\powershell\Bin #> $s.psobject.typenames
System.Object
SuperSite.SuperSite
SuperSite.SuperSite#site

At the same time, as you could remember from the previous posts, output template for sites and default properties set are defined for objects that have entry 'Microsoft.IIs.PowerShell.Framework.Site#site' in this collection. PowerShell simply cannot find any definition for our super sites in its tables. There are two ways to fix this. First, we could redefine default properties and output template in our own files and load these files when we start PowerShell session, like we did in previous post. You could copy definitions for site from the original file and replace type name by SuperSite.SuperSite#site. Or, second way, if you are OK with existing definitions and do not have plans to expose your new properties in default set or output, we could override one more method in our SuperSite and restore TypeNames collection. Let's do that.

Add reference to System.Management.Automation into 'using' section. We will need this because we are going to deal with PSObject from this assembly. You could extract this assembly from the GAC using the same method as we did for IIS provider framework, or you could use any official PowerShell SDK to obtain reference assemblies.

Then add method to the SuperSite:

public override PSObject ToPSObject()
{
    PSObject obj = base.ToPSObject();

    string myType = this.GetType().FullName;
    string parentType = typeof(Site).FullName;
    string typeTag = this.TypeInfo.Name;
    obj.TypeNames.Remove(myType);
    obj.TypeNames.Remove(myType + "#" + typeTag);
    obj.TypeNames.Add(parentType);
    obj.TypeNames.Add(parentType + '#' + typeTag);

    return obj;
}

Now build this again

e:\dev\powershell\SuperSite>csc /r:Microsoft.IIS.PowerShell.Framework.dll /r:System.Management.Automation.dll /target:library /out:SuperSite.dll /keyfile:test.snk SuperSite.cs

Then replace GACed assembly using commands from above and restart PowerShell. This time you should see regular output. You could send requests to some of your sites to see how perf counter is updated.

You could download files for this post from here.

Enjoy,

Sergei

No Comments