December 2008 Blog Posts

Our domain registrar has had major issues with their nameservers over the last 24 hours. This has meant that our web site has been inaccessible for a long period of time.

The provider in question has informed us that the issue has been resolved, but it may take up to 24 hours for DNS replication before the site becomes accessible globally once more.

Thanks for your patience.

 

This is the fifth part in a series of tutorials aimed at guiding novice developers though building a "Hello World" Windows Home Server Add-In, using only freely-available tools (including Visual Studio Express).

In Part 5, the last part of our tutorial, we'll wrap our Add-In as part of an MSI package so we can deploy it using the Windows Home Server Console.

For this tutorial, we're building a Windows Home Server Add-In that detects changes to Shared Folder permissions. You can access the other parts of the tutorial, or download the code I'm using in the screenshots, using the links below.

  • Part 1: Installing and configuring your development environment
  • Part 2: Designing a GUI and initial deployment test
  • Part 3: Responding to Windows Home Server Notifications (code)
  • Part 4: Using FancyListView and comparing permissions (code)
  • Part 5: Building an installer package using WiX (code)

Note: The code samples below don't copy and paste very well directly into Visual Studio. If you don't want to type them out by hand, copy the sample into WordPad, and then copy from WordPad into Visual Studio.

 

Part 1: Add a new XML file

Creating an installer package for our Add-In is relatively straightforward. We just need to create a new XML file as part of our solution that contains the metadata needed by WiX to build the correct MSI file.

Right-click our project and add a New Item. Choose XML File and name the file installer.wxs.

139-wix 140-wix

You'll be presented with a new, blank, XML file. The easiest way to build WiX files is to reuse an existing one, so we'll use the same XML data I use for WHS Disk Management.

141-wix 142-wix

You'll want to change the Manufacturer information, of course. Also, where you see GUIDs (UpgradeCode and Component Guid), you'll want to generate your own unique values (use the same tool as last time: http://www.famkruithof.net/uuid/uuidgen).

Since we're only adding a single file, the XML isn't too complex. Note the Component Source directory path; you'll want to make sure this is correct on your development machine (you're probably not logged in as Administrator, for example).

The other critical section is the Property identified as WHSLogo. The Windows Home Server Console checks this value to determine if your MSI file is a valid WHS Add-In, so make sure you set it to 1.

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product
    Name="My First Add-In"
    Id="*"
    UpgradeCode="{1a7c55f0-ceed-11dd-ad8b-0800200c9a66}"
    Manufacturer="Sam Wood - Tentacle Software"
    Version="0.0.0.1"
    Language="1033">
    <Package
      Manufacturer="Sam Wood - Tentacle Software"
      InstallerVersion="200"
      Languages="1033"
      Compressed="yes" />
 
    <Media Id="1" Cabinet="MyFirstAddIn.0.0.0.1.cab" EmbedCab="yes" />
 
    <Property Id="WHSLogo">1</Property>
 
    <Condition Message="[ProductName] requires Windows Home Server. For more information, please refer to the User Guide.">VersionNT = 502</Condition>
 
    <Directory 
      Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder" Name="PFiles">
        <Directory Id="WHS" Name="Windows Home Server">
          <Component 
            Id="HomeServerConsoleTab.My_First_Add_In" 
            Guid="{47d62350-ceed-11dd-ad8b-0800200c9a66}">
            <File 
              Id="HomeServerConsoleTab.My_First_Add_In.dll" 
              Name="HomeServerConsoleTab.My_First_Add_In.dll" 
              Source="C:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects\My First Add-In\My First Add-In\bin\Release\HomeServerConsoleTab.My_First_Add_In.dll" 
              Vital="yes" 
              KeyPath="yes" 
              DiskId="1"/>
          </Component>
        </Directory>
      </Directory>
    </Directory>
 
    <Feature Id="ProductFeature" Title="MyFirstAddIn" Level="1">
      <ComponentRef Id="HomeServerConsoleTab.My_First_Add_In" />
    </Feature>
  </Product>
</Wix>

 

Part 2: Create a batch file

If you're lucky enough to own a copy of Visual Studio (that isn't the Express edition), WiX will integrate with your development environment. That means you can build MSI packages from within Visual Studio as part of your normal project build process.

Because we're using Visual Studio Express, we don't get that integration. We'll have to use the WiX command-line tools to get the job done.

To make things easier, we'll create a batch file so that we don't have to type the same commands over and over (trust me, you'll be doing a lot of MSI building if you're a WHS developer!). Right-click the project and add a New Item. This time, we'll create a text file.

Call it installer.bat

143-wix

Visual Studio creates a text file with an encoding that doesn't quite work. That's OK, it just means that the first line in our batch file will error out; we'll make sure our first line doesn't really matter.

Add the following text to our batch file and save it. Again, note our paths pointing to the \bin\release folder of our project; make sure they match the paths on your development machine. The quotes are important too, don't forget them!

pause
"C:\Program Files\Windows Installer XML v3\bin\candle" -out "C:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects\My First Add-In\My First Add-In\bin\Release\install.wixobj" "C:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects\My First Add-In\My First Add-In\installer.wxs"
"C:\Program Files\Windows Installer XML v3\bin\light" "C:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects\My First Add-In\My First Add-In\bin\Release\install.wixobj" -out "C:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects\My First Add-In\My First Add-In\bin\Release\MyFirstAddIn.0.0.0.1.msi"

144-wix

Once you're done, save the file. Navigate to your project's directory and run the installer.bat file. You should see candle and light building the MSI package.

145-wix 146-wix

When the command prompt disappears, check the \bin\release folder of your project for the new MSI package.

If it's not there, you might be missing some errors that candle or light are throwing; you can add another pause at the end of the batch file to stop the command prompt closing until you press a key.

 

Step 3: Installing the Add-In

You'll want to clean up our test Add-In file from the previous tutorials so that we can verify our installer is actually installing files. Delete HomeServerConsoleTab.My_First_Add_In.dll from C:\Program Files\Windows Home Server\ on your server.

Copy the MSI file to \Software\Add-Ins on your server.

147-wix 148-wix

Now, open the WHS Console, click Settings and change to the Add-Ins tab. Under Available Add-Ins, our Add-In should be listed. Let's install it now.

149-wix 150-wix

When the installation is complete, WHS will prompt you to restart the console. Log in again, and, through the magic of television, our Add-In has been installed and is working correctly!

Try uninstalling the Add-In through Settings, and verify that our MSI package can be uninstalled as expected.

151-wix

Well, that's it for this tutorial. You've created and deployed a Windows Home Server Add-In completely from scratch, and it actually works! Congratulations.

Just upgraded the blog from Subtext 1.9.5 to 2.1. It was a seamless process; I spent more time trying to get a decent database backup than actually running the upgrade wizard.

I’ll play around with some skins now, so don’t be alarmed if we suddenly turn bright purple.

 

This is the fourth part in a series of tutorials aimed at guiding novice developers though building a "Hello World" Windows Home Server Add-In, using only freely-available tools (including Visual Studio Express).

In Part 4, we'll clean up the GUI to make it look a bit more shiny, and add our share permission change notification functions.

For this tutorial, we're building a Windows Home Server Add-In that detects changes to Shared Folder permissions. You can access the other parts of the tutorial, or download the code I'm using in the screenshots, using the links below.

  • Part 1: Installing and configuring your development environment
  • Part 2: Designing a GUI and initial deployment test
  • Part 3: Responding to Windows Home Server Notifications (code)
  • Part 4: Using FancyListView and comparing permissions (code)
  • Part 5: Building an installer package using WiX (code)

Note: The code samples below don't copy and paste very well directly into Visual Studio. If you don't want to type them out by hand, copy the sample into WordPad, and then copy from WordPad into Visual Studio. 

 

Step 1: Create a new object

As we talked about briefly last time, we're building an Add-In that will alert us when permissions are changed for our shared folders.

In order to compare the current shared folder permissions with some arbitrary known-good state, we need a way to save the permissions of all our shared folders to disk. That way, when the WHS Console is opened, we can retrieve our known-good state and compare it to the current set of permissions.

We're going to need a new object to represent our saved share permissions. Right-click our project and choose Add Class.

113addlist

Add the new class as SharePermissions.cs.

 114addlist

A new file will open in Visual Studio; this is our object. Change the generated code to look like this:

using System.Collections.Generic;
using Microsoft.HomeServer.SDK.Interop.v1;
 
namespace Microsoft.HomeServer.HomeServerConsoleTab.My_First_Add_In
{
    public class SharePermissions
    {
        private string shareName;
        private List<WHSUserPermission> permissions;
 
        public SharePermissions()
        {
            permissions = new List<WHSUserPermission>();
        }
 
        public string ShareName
        {
            get { return shareName; }
            set { shareName = value; }
        }
 
        public List<WHSUserPermission> Permissions
        {
            get { return permissions; }
            set { permissions = value; }
        }
    }
}

 

We've now created an object that can hold a share name, and a list of WHSUserPermission objects. The WHSUserPermission objects are what we played with in the last part of the tutorial.

At the top of our MainTabUserControl form, add a couple of named List<SharePermissions> objects. These will hold our known-good state and the current state of shared folder permissions.

List<SharePermissions> AcceptedPermissions;
List<SharePermissions> CurrentPermissions;

 115addlist

 

Step 2: Get ready for XML serialization

We need a way to save our new objects to disk. We'll do that with XML Serialization. Add the following using declarations to the top of the MainTabUserControl file:

using System.IO;
using System.Xml;
using System.Xml.Serialization;

 116addlist

We'll make use of an ApplicationFolder, a great Windows Home Server feature, to determine a physical location to save our XML file. Add a named instance of IApplicationFolder under our two List<SharePermissions> objects.

IApplicationFolder AppFolder;

 117addlist

The setup of an ApplicationFolder requires us to provide Windows Home Server with a unique identifier. Generate a random GUID from http://www.famkruithof.net/uuid/uuidgen, and use it in the code below (don't just reuse the GUID in the code below; you don't want to use the same application folder as another Add-In author).

We'll try to find our ApplicationFolder using this GUID, and if it doesn't exist (if this is the first time loading the Add-In in the console, for example) we'll ask WHS to create a new one.

Add the following code to the constructor for MainTabUserControl:

try
{
    this.AppFolder = whsInfoClass.GetApplicationFolder(new Guid("a607657c-6b11-45e5-8ba6-885c57b19b21"));
}
catch (Exception)
{
    this.AppFolder = whsInfoClass.CreateApplicationFolder(new Guid("a607657c-6b11-45e5-8ba6-885c57b19b21"), "My First Add-In");
}

 118addlist

 

Step 3: Populate and save our new object

We're going to move a few chunks of our previous logic around to make things a bit more usable. Comment out the bulk of our DisplaySharePermissions method like this:

private void DisplaySharePermissions()
{
    this.fancyListView1.Items.Clear();
 
    //foreach (IShareInfo2 info in whsInfoClass.GetShareInfo2())
    //{
    //    Array perms;
    //    info.GetPermissions(out perms);
    //    WHSUserPermission[] permissions = perms as WHSUserPermission[];
 
    //    for (int i = 0; i < permissions.Length; i++)
    //    {
    //        ListViewItem permissionItem = new ListViewItem(info.Name);
    //        permissionItem.SubItems.Add(permissions[i].userName);
    //        permissionItem.SubItems.Add(permissions[i].permission.ToString());
    //        fancyListView1.Items.Add(permissionItem);
    //    }
    //}
}

 

Under DisplaySharePermissions, add a new method called GetCurrentSharePermissions. This method does basically the same thing as the chunk of code we commented out above.

The method loops through the IShareInfo2 array returned by Windows Home Server, and creates new SharePermissions objects for each shared folder. It then adds each WHSUserPermission object to the List<WHSUserPermission> in the SharePermissions object.

Finally, we return the fully populated list that represents all the shared folders on our server and all the permissions of those shared folders.

private List<SharePermissions> GetCurrentSharePermissions()
{
    List<SharePermissions> currentPermissions = new List<SharePermissions>();
 
    foreach (IShareInfo2 info in whsInfoClass.GetShareInfo2())
    {
        Array perms;
        info.GetPermissions(out perms);
        WHSUserPermission[] permissions = perms as WHSUserPermission[];
 
        SharePermissions share = new SharePermissions();
        share.ShareName = info.Name;
 
        for (int i = 0; i < permissions.Length; i++)
        {
            share.Permissions.Add(permissions[i]);
        }
 
        currentPermissions.Add(share);
    }
 
    return currentPermissions;
}

 121addlist

We also need to load, or deserialize, our known-good state from disk when the Add-In opens. Add a new method under GetCurrentSharePermissions called GetAcceptedSharePermissions.

This method tries to retrieve our XML file from our ApplicationFolder and read it. If that fails, we assume this is the first time we've opened the Add-In (so we haven't saved a known-good state yet) and we just use GetCurrentPermissions to populate the object.

private List<SharePermissions> GetAcceptedSharePermissions()
{
    List<SharePermissions> acceptedPermissions;
 
    try
    {
        Stream XmlFileStream = new FileStream(AppFolder.Path + @"\" + "AcceptedPermissions.xml", FileMode.Open);
        XmlSerializer Serializer = new XmlSerializer(typeof(List<SharePermissions>));
 
        acceptedPermissions = (List<SharePermissions>)Serializer.Deserialize(XmlFileStream);
 
        XmlFileStream.Close();
    }
    catch (Exception)
    {
        acceptedPermissions = GetCurrentSharePermissions();
    }
 
    return acceptedPermissions;
}

 122addlist

We need to save, or serialize, our AcceptedPermissions object to disk so we can load it back next time the console opens. Add a new method called SaveAcceptedSharePermissions.

private void SaveAcceptedSharePermissions()
{
    try
    {
        Stream XmlFileStream = new FileStream(AppFolder.Path + @"\" + "AcceptedPermissions.xml", FileMode.Create, FileAccess.ReadWrite);
        XmlSerializer Serializer = new XmlSerializer(typeof(List<SharePermissions>));
        XmlTextWriter Writer = new XmlTextWriter(XmlFileStream, Encoding.UTF8);
        Writer.Formatting = Formatting.Indented;
 
        if (this.AcceptedPermissions != null)
        {
            Serializer.Serialize(Writer, this.AcceptedPermissions);
        }
 
        XmlFileStream.Close();
    }
    catch (Exception)
    {
        MessageBox.Show("Unable to save Accepted Permissions!");
    }
}

 123addlist

In our MainTabUserControl constructor, let's call our Get methods to populate the two List<SharePermissions> objects when the Add-In loads so we can start comparing them.

this.AcceptedPermissions = GetAcceptedSharePermissions();
this.CurrentPermissions = GetCurrentSharePermissions();

 124addlist

We'll also change the function of our button.

Edit the Click event handler for the button so that we're updating our known-good state to match the current state, saving it to disk, and then updating the GUI.

private void consoleToolBarButton1_Click(object sender, EventArgs e)
{
    //this.listBox1.Items.Clear();
    AcceptedPermissions = CurrentPermissions;
    SaveAcceptedSharePermissions();
    DisplaySharePermissions();
}

 128addlist

We don't really care about the other notifications we've been watching. They were a great way to demonstrate some WHS functionality, but we don't want to process them anymore.

Expand the INotificationCallback region if  it's closed.

  126addlist 127addlist

Then comment out our processing in the NotificationChanged method.

public void NotificationChanged(string UniqueID, WHS_Notification_Type Type, WHS_Notification_Severity Severity, int IsSuppressed, string textHeader, string textDescription, string helpFilename, string helpSection, string helpLinkText)
{
    //throw new NotImplementedException();
    //this.listBox1.Items.Add(Type + " " + Severity + ": " + textHeader + " " + textDescription);
}

 

Step 4: Comparing known-good and current states

Now we're going to do something with our two lists of shared folder permissions.

As administrators, we care if the permissions assigned to shared folders change. We want to compare the current state with our known-good state to see if anyone's been tampering with things they shouldn't.

We'll add a big chunk of code to our DisplaySharePermissions method. This method holds most of our logic.

First, we clear fancyListView1 to avoid constantly adding duplicate items.

Then we loop through our CurrentPermissions and compare them with our AcceptedPermissions. If a user has a different permission than what we expect, we flag that entry and mark it red. If this is a new user, or a new share, we mark that entry green.

You'll notice that we're calling BeginUpdate and EndUpdate on fancyListView1. This freezes the control until we're done updating it. If you don't do this, you'll get some nasty flickering or a completely scrambled view, depending on how you're updating the FancyListView.

private void DisplaySharePermissions()
{
    this.fancyListView1.BeginUpdate();
 
    this.fancyListView1.Items.Clear();
    this.fancyListView1.Groups.Clear();
 
    foreach (SharePermissions share in CurrentPermissions)
    {
        ListViewGroup group = new ListViewGroup(share.ShareName);
        fancyListView1.Groups.Add(group);
 
        foreach (WHSUserPermission permission in share.Permissions)
        {
            ListViewItem permissionItem = new ListViewItem(string.Empty);
            permissionItem.Group = group;
 
            permissionItem.SubItems.Add(permission.userName);
            permissionItem.SubItems.Add(permission.permission.ToString());
 
            foreach (SharePermissions acceptedShare in AcceptedPermissions)
            {
                if (acceptedShare.ShareName == share.ShareName)
                {
                    foreach (WHSUserPermission acceptedPermission in acceptedShare.Permissions)
                    {
                        if (acceptedPermission.userName == permission.userName)
                        {
                            if (acceptedPermission.permission != permission.permission)
                            {
                                permissionItem.ForeColor = Color.Red;
                            }
                            else
                            {
                                permissionItem.ForeColor = Color.Black;
                            }
 
                            break;
                        }
 
                        permissionItem.ForeColor = Color.Green;
                    }
 
                    break;
                }
                else
                {
                    permissionItem.ForeColor = Color.Black;
                }
 
                permissionItem.ForeColor = Color.Green;
            }
 
            fancyListView1.Items.Add(permissionItem);
        }
    }
 
    this.fancyListView1.EndUpdate();
}

 129addlist

 

Step 5: Tweaking the GUI

Go back to our MainTabUserControl design view by opening Solution Explorer and double-clicking MainTabUserControl.cs.

Remove listBox1 by selecting it once (Visual Studio has a tendency to freeze when you first open the designer; just let it finish, it hasn't locked up) and then use the delete key.

Drop a PictureBox in from the ToolBox, and set it to dock in parent (click the little arrow at the top of the PictureBox and select Dock in Parent).

 130addlist 131-add-list

Select our button and open the Properties pane. Change the Text property to "Accept Current Permissions".

 132-add-list

Right-click one of the panes of our SplitContainer and choose Select 'splitContainer1'. Open the Properties pane and set IsSplitterFixed to true, SpiltterWidth to 1, and SplitterDistance to 100.

  133addlist 134addlist

Save the updates we've made, and change back to code view (open Solution Explorer, right-click MainTabUserControl.cs and choose View Code).

Go crazy with some image manipulation to make our Add-In a little prettier. Add the following to our MainTabUserControl constructor.

string title = "My First Add-In";
StringFormat format = new StringFormat(StringFormatFlags.DirectionVertical);
Font font = new Font("Tahoma", 40, FontStyle.Bold);
 
Image image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
Graphics graphics = Graphics.FromImage(image);
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
 
graphics.FillRectangle(new SolidBrush(Color.FromArgb(228, 235, 243)), new Rectangle(0, 0, image.Width, image.Height));
graphics.DrawImage(Microsoft.HomeServer.Common.Client.CommonImages.WizardTopBannerImage, new Point(0,0));
graphics.DrawString(title, font, new SolidBrush(Color.FromArgb(57, 69, 90)), new PointF(15, 75), format);
 
this.pictureBox1.Image = image;

 135addlist

Time to build and deploy this magnificent thing. If you don't remember from last time:

Right-click the project in Solution Explorer and choose Build. Copy the compiled Add-In (probably in C:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects\My First Add-In\My First Add-In\bin\Release\, named HomeServerConsoleTab.My_First_Add_In.dll) to your Windows Home Server's C:\Program Files\Windows Home Server\ folder.

Open the console and enjoy the spectacle. Make sure to click the button to save our first known-good state.

 136addlist

Congratulations! You have an Add-In that actually does something interesting!

 

Step 6: Investigating functionality

Change some shared folder permissions and watch how the Add-In reacts. Click the button to accept these changes as our known good state.

  137addlist 138addlist

Play around with changing permissions, then close and reopen the console. Our previous known-good state is retrieved from disk and the permissions changes you made will still be marked red.

Have a look at D:\folders\{A607657C-6B11-45E5-8BA6-885C57B19B21}\ (or whatever your custom GUID is). You'll see our AcceptedPermissions.xml file that we saved to our ApplicationFolder. Open the file up to see the structure.

Obviously there are some design issues here.

What happens if someone changes permissions and then just clicks the "Accept Current Permissions" button? Well, we won't know that anything's wrong, will we? The Add-In will be doing what its told and saving the new known-good state.

We could get around that by saving each permission change to a log file somewhere, or by firing off a Windows Home Server health alert. But this is only a tutorial to get you started, and not a design guide for securely auditing shared folder permissions, right?

Next time, we'll cover deploying Windows Home Server Add-Ins properly, using MSI packages. After all, no user wants to manually copy a DLL file to their server to install an Add-In.

Go vote!

I've enabled comments on the blog. If the spam gets too bad I'll turn commenting off again, but for now, let's see how it goes.

Next part of the tutorial coming up this weekend.

This is the third part in a series of tutorials aimed at guiding novice developers though building a "Hello World" Windows Home Server Add-In, using only freely-available tools (including Visual Studio Express).

For Part 3, we'll actually be writing some code and responding to Windows Home Server notifications.

For this tutorial, we're building a Windows Home Server Add-In that detects changes to Shared Folder permissions. You can access the other parts of the tutorial, or download the code I'm using in the screenshots, using the links below.

  • Part 1: Installing and configuring your development environment
  • Part 2: Designing a GUI and initial deployment test
  • Part 3: Responding to Windows Home Server Notifications (code)
  • Part 4: Using FancyListView and comparing permissions (code)
  • Part 5: Building an installer package using WiX (code)

 

Step 1: Adding References

Before we can interact with Windows Home Server components, we need to tell our Add-In code where it can find the WHS-specific libraries it needs.

Instead of using the designer, we will edit our Add-In code directly this time. Right-click MainTabUserControl.cs in Solution Explorer, and choose View Code. You'll see a bunch of using statements at the top of the code file, and a couple of methods under those.

Back in Solution Explorer, right-click the References folder and choose Add Reference.

074-Add-WHSInfo 075-Add-WHSInfo

Scroll down in the list, choose microsoft.homeserver.sdk.interop.v1 and click OK. Unpin Solution Explorer for now.

076-Add-WHSInfo 077-Add-WHSInfo

At the bottom of your list of using statements, add a reference to the library:

using Microsoft.HomeServer.SDK.Interop.v1;

078-Add-WHSInfo

Now our code knows about a whole bunch of WHS interoperability functions. In order to access those functions, we need to create an object that will talk to Windows Home Server for us.

Underneath the IConsoleServices object, add a WHSInfoClass. We add the object at this level so all of the code in our MainTabUserControl can access it. The first part of the statement tells the compiler what type of object this is (WHSInfoClass, in our case), and the second part is the name we're going to use to refer to this particular instance of the type (whsInfoClass for us).

WHSInfoClass whsInfoClass;

079-Add-WHSInfo

Once we've defined how we're going to access WHSInfoClass, we need to create an instance of it that we can interact with. In the constructor for our MainTabUserControl, add the following:

this.whsInfoClass = new WHSInfoClass();

080-Add-WHSInfo

We're now ready to start interacting with WHS in our code.

 

Step 2: Receiving Notifications

We haven't really talked about what our Add-In is going to do yet; it's a secret. First, we're going to tell our Add-In to listen to WHS for notifications.

Windows Home Server is very chatty; all we have to do is tell WHS that we're listening, and WHS will share all sorts of interesting information about the current state of the server, in real time. We're going to tap into the notification infrastructure and listen out for some specific events.

First, we need to change our MainTabUserControl to implement some specific code. Tack INotificationCallback on the end of the line, like so:

public partial class MainTabUserControl : UserControl, INotificationCallback

081-Add-WHSInfo

Right click INotificationCallBack and choose Implement Interface.

082-Add-WHSInfo

Visual Studio helpfully adds the code that makes up INotificationCallback for us. This code will be a contract between our Add-In and WHS; we agree to implement this code exactly as WHS expects so that WHS can send us notifications.

This is the code added automatically for us:

#region INotificationCallback Members
 
public void BackupStateChanged(WHSBackupState State)
{
    throw new NotImplementedException();
}
 
public void Disconnected()
{
    throw new NotImplementedException();
}
 
public void NotificationChanged(string UniqueID, WHS_Notification_Type Type, WHS_Notification_Severity Severity, int IsSuppressed, string textHeader, string textDescription, string helpFilename, string helpSection, string helpLinkText)
{
    throw new NotImplementedException();
}
 
public void PhysicalDiskChanged(IDiskInfo pDiskInfo)
{
    throw new NotImplementedException();
}
 
public void ReConnected()
{
    throw new NotImplementedException();
}
 
public void UserInfoChanged()
{
    throw new NotImplementedException();
}
 
#endregion
 

 

The first thing we want to do is comment out all the exceptions. A couple of forward slashes at the beginning of the line tells the compiler to ignore this code.

#region INotificationCallback Members
 
public void BackupStateChanged(WHSBackupState State)
{
    //throw new NotImplementedException();
}
 
public void Disconnected()
{
    //throw new NotImplementedException();
}
 
public void NotificationChanged(string UniqueID, WHS_Notification_Type Type, WHS_Notification_Severity Severity, int IsSuppressed, string textHeader, string textDescription, string helpFilename, string helpSection, string helpLinkText)
{
    //throw new NotImplementedException();
}
 
public void PhysicalDiskChanged(IDiskInfo pDiskInfo)
{
    //throw new NotImplementedException();
}
 
public void ReConnected()
{
    //throw new NotImplementedException();
}
 
public void UserInfoChanged()
{
    //throw new NotImplementedException();
}
 
#endregion
 

 

We need to tell WHS that when our Add-In loads it's ready to listen for notifications. Add this to the bottom of our MainTabUserControl constructor:

this.whsInfoClass.RegisterForNotifications(this);

083-Add-WHSInfo

 

Step 3: Responding to Notifications

Right now, our Add-In can receive notifications from WHS. That's great, but we haven't written any code to actually do anything about those notifications.

Each method in INotificationCallback gets called by WHS depending on which notification it's trying to send us. Right now, we want a generic catch-all; we want to see and act on all the normal notifications WHS adds to the notification queue.

Let's change our NotificationChanged method, and add some quick code to dump out each notification to our ListBox so we can see it.

Notice that WHS sends us a bunch of information about the notification that we can display or act on in other ways. We'll use the Type, Severity, textHeader, and textDescription parameters to add an entry to our ListBox.

public void NotificationChanged(string UniqueID, WHS_Notification_Type Type, WHS_Notification_Severity Severity, int IsSuppressed, string textHeader, string textDescription, string helpFilename, string helpSection, string helpLinkText)
{
    //throw new NotImplementedException();
    this.listBox1.Items.Add(Type + " " + Severity + ": " + textHeader + " " + textDescription);
}

084-Add-WHSInfo

We're also interested in this UserInfoChanged thing. We'll add another quick line to tell us when this gets called by WHS. Unfortunately, WHS doesn't send us any information for this method, so the only information we have is that WHS fired this notification at us.

public void UserInfoChanged()
{
    //throw new NotImplementedException();
    this.listBox1.Items.Add("User info changed!");
}

085-Add-WHSInfo

We're going to get a lot of information showing up in our ListBox. We'll make use of our button to clear the list when it gets too long.

Open MainTabUserControl.cs in the designer (open Solution Explorer and double-click it, remember?) and select our button. Expand the Properties pane.

By default, the Properties pane shows us properties of the selected object (funny that), like background colour, text, and size. But if you click the little lightning bolt icon in the Properties pane toolbar, you'll change to the events tab.

Click the lightning bolt to change to the events tab for our button, and then double-click the Click event. This will jump you back to viewing your code, with a new Click event handler created. This method gets called every time you click the button.

089-Add-button-event 090-Add-button-event

Add some code to the event handler to clear the ListBox.

private void consoleToolBarButton1_Click(object sender, EventArgs e)
{
    this.listBox1.Items.Clear();
}

091-Add-button-event

Let's build this sucker. Expand Solution Explorer again, right-click the project and choose Build.

086-Add-WHSInfo

Copy the compiled Add-In to Windows Home Server using Remote Desktop, as we did yesterday. Make sure the WHS Console is closed, or you won't be able to overwrite the file (you may have to open and then close the Console again to kill any other sessions that have it open remotely).

071-Build-assembly 072-Build-assembly

Now open up the WHS Console and have a look at your creation. There's lots of text here, so drag your SplitView over to the right to see our notifications. If anything changes (you add a new share, or a disk suddenly goes offline), the notification will show up here.

Let's explore that UserInfoChanged notification. Jump over to the User Accounts tab and create a new user. Have a look at the notifications sent to our Add-In.

UserInfoChanged gets called when we add or remove users, and when permissions are changed on folders, so when the new user creation process adds the new user to share permissions, we get notified (a lot).

087-Test-WHSInfo 088-Test-WHSInfo

Click the toolbar button to clear the ListBox. Got to the Shared Folders tab and change user permissions on a shared folder. Check back to see that we were notified.

092-Test-WHSInfo

That's cool, but what can we do with that notification? Hmm... what if we want to keep track of which users have permissions to specific shared folders, and throw an alert if something is changed?

 

Step 4: Extracting Information from Windows Home Server

Change back to Visual Studio. Open MainTabUserControl.cs in the designer.

Select the FancyListView, and then click the arrow in the top right corner. Choose Edit Columns, and then change each column's Text property.

The first column should be "Share",  the second should be "Username, and the third should be "Permission".

093-Add-share-permissions 094-Add-share-permissions

Change back to code view.

We're going to add a method that uses GetShareInfo2 from our WHSInfoClass instance. GetShareInfo2 is just one of the many methods that WHSInfoClass exposes; you can interrogate Windows Home Server for all sorts of juicy information using just WHSInfoClass.

GetShareInfo2 returns an array of objects that represent all the shares that WHS knows about, including the name, path, and current permissions of the share.

Our method will cycle through each share object and extract each permission assigned to that share. The permissions information will be displayed in our FancyListView.

Add a new method at the bottom of our code file:

private void DisplaySharePermissions()
{
    this.fancyListView1.Items.Clear();
 
    foreach (IShareInfo2 info in whsInfoClass.GetShareInfo2())
    {
        Array perms;
        info.GetPermissions(out perms);
        WHSUserPermission[] permissions = perms as WHSUserPermission[];
 
        for (int i = 0; i < permissions.Length; i++)
        {
            ListViewItem permissionItem = new ListViewItem(info.Name);
            permissionItem.SubItems.Add(permissions[i].userName);
            permissionItem.SubItems.Add(permissions[i].permission.ToString());
            fancyListView1.Items.Add(permissionItem);
        }
    }
}

099-Add-share-permissions

Fire our new method from UserInfoChanged, so that when WHS tells us that share permissions have changed we run our permissions enumeration code.

public void UserInfoChanged()
{
    //throw new NotImplementedException();
    this.listBox1.Items.Add("User info changed!");
    DisplaySharePermissions();
}

096-Add-share-permissions

Do the same thing to our MainTabUserControl constructor, so we pull down the current share permissions when our Add-In loads.

097-Add-share-permissions

Build the Add-In (right-click the project in Solution Explorer and choose Build). Copy the compiled Add-In to your server using Remote Desktop, and reopen the console.

098-Add-share-permissions

Success!

For fun, change some permissions on a shared folder and see that they get updated in our FancyListView.

Unfortunately, our FancyListView doesn't look very fancy at the moment. And the ListBox was just there for testing purposes; now that we know what WHS notifications look like the ListBox needs to be removed and replaced with a shiny graphic of some kind.

We'll make everything look pretty next time, along with adding some code to alert our administrator when a Shared Folder permission has been changed.

 

This is the second part in a series of tutorials aimed at guiding novice developers though building a "Hello World" Windows Home Server Add-In, using only freely-available tools (including Visual Studio Express).

In Part 2, we'll create the GUI for our Add-In and actually see it running in the Windows Home Server Console.

For this tutorial, we're building a Windows Home Server Add-In that detects changes to Shared Folder permissions. You can access the other parts of the tutorial, or download the code I'm using in the screenshots, using the links below.

  • Part 1: Installing and configuring your development environment
  • Part 2: Designing a GUI and initial deployment test
  • Part 3: Responding to Windows Home Server Notifications (code)
  • Part 4: Using FancyListView and comparing permissions (code)
  • Part 5: Building an installer package using WiX (code)

 

Step 1: Creating the project

Open Visual Studio Express, and close that annoying start page. From the File menu, choose New Project and pick our Home Server Add-In template from the list. Give your project an appropriately imaginative name (I've used My First Add-In here).

040-Open-VS 041-Create-project

Once the project has loaded, click the Save All button in the toolbar. Visual Studio will prompt you for a location; accept the defaults and click Save.

We need to change a couple of things in the project file to get the Windows Home Server Console to see our Add-In correctly. Right-click the project and choose Properties.

042-Save-project 043-Edit-project

Change the Target Framework option to .NET Framework 2.0. You'll be prompted to close and reopen the project; say yes to this.

044-Edit-project 045-Edit-project

Let's unpin the empty Toolbox toolbar to give ourselves a bit more room. Click the pin icon at the top of the Toolbox.

Open the project properties again by right-clicking the project and choosing Properties (as we did above).

Our Assembly Name needs to be HomeServerConsoleTab.<Add-In Name> (which happens to be the last two sections of the Default Namespace). All Add-Ins need to follow this naming convention, otherwise the WHS Console won't load them.

Change our Assembly Name to HomeServerConsoleTab.My_First_Add_In.

Click Assembly Information and change the version numbers to 0.0.0.1; this is most definitely not a v1.0 Add-In yet!

046-Edit-project 047-Edit-project

While you're here, check out the Resources tab. You'll see a single bitmap; that's going to be the icon displayed for our Add-In. Save and then close the properties window.

048-Edit-project

 

Step 2: Adding Graphical User Interface elements

Open MainTabUserControl.cs in the designer by double-clicking it in the Solution Explorer pane. Unpin Solution Explorer so we have more room, and then repin the Toolbox (hover over the Toolbox tab to open it, then click the pin icon again).

You'll see all the lovely WHS controls we added yesterday. Right-click the Windows Home Server header and choose Sort Items Alphabetically.

049-Edit-Controls 050-Edit-Controls

The view we're looking at is the design surface for the main tab of our Add-In. Drag a ConsoleToolBar from the Toolbox and drop it on to this design surface.

Click drop-down menu at the beginning of the new ConsoleToolBar and choose ConsoleToolBarButton to add a new button to the toolbar.

051-Edit-Controls 052-Edit-Controls

Right-click the new button, and change the DisplayStyle of the button to ImageAndText. Right-click the button again, and this time open Properties.

053-Edit-Controls 054-Edit-Controls

You'll get a new pane opening where Solution Explorer used to be. These settings are the properties for the currently selected item in the designer (in this case, our ConsoleToolBarButton).

Scroll down in the list of properties until you find Text, and change the value to My First Button. We also want to change the Image property, so click the picture next to Image, and then click the browse button that shows up.

Select the exclamation point image from our project's resources. Click OK, and your button's image will update.

055-Edit-Controls 056-Edit-Controls

Unpin the Properties window to clear our working area a bit.

057-Edit-Controls

Let's change the background colour of our tab. Click the main area of the tab to select it, then expand the Properties pane again. Scroll down to BackColor and change the colour to Transparent.

We do this so that the background colour of the WHS Console shows through later.

058-Edit-Controls 059-Edit-Controls

Time to add some other components. Drag and drop a SplitContainer into the main area of the tab. Notice how it expands to fill the available area automatically; this is called docking. Now drop a FancyListView into the area labelled Panel2.

060-Edit-Controls 061-Edit-Controls

There's a little arrow at the top of the currently selected FancyListView; click the arrow and choose Dock in Parent Container. Now click Edit Columns.

062-Edit-Controls 063-Edit-Controls

Add a few columns and click OK. Resize the columns a bit; this will be the default width of the columns when the Add-In is first opened.

064-Edit-Controls 065-Edit-Controls

Drop a ListBox into Panel1. Expand the properties pane again, and change the Dock property of the ListBox to Fill by clicking the middle button.

066-Edit-Controls 067-Edit-Controls

Save your masterpiece.

068-Edit-Controls

 

Step 3: Building and deployment

Unpin Toolbox to get it out of the way, and pin Solution Explorer open. Right-click the project and choose Build. Visual Studio runs off and compiles your Add-In into an assembly (a library, or .DLL file).

069-Build-assembly 070-Build-assembly

Visual Studio saves the compiled Add-In to a subfolder of the project.

By default, this subfolder will be My Documents\Visual Studio 2008\Projects\My First Add-In\My First Add-In\bin\Release. There are a number of files in here, but the only one we care about is HomeServerConsoleTab.My_First_Add_In.dll; this is our compiled Add-In.

Open a Remote Desktop session to your server (we did this last time; Start, Run, mstsc -console, remember?). On your server, open C:\Program Files\Windows Home Server\.

Copy and paste your compiled Add-In from your PC to the folder on your server, through your remote desktop session.

If copy/paste won't work, you forgot to check Drives under Local Resources for the Remote Desktop connection. See Step 4 of Part 1.

071-Build-assembly 072-Build-assembly

Now, open the console.

Through the magic of television, your new Add-In is loaded by the Windows Home Server Console. Click your Add-In's icon to change to your tab, and marvel at how awesome it looks.

Of course, the buttons don't do anything yet, but that's ok; you should be celebrating just getting this far!

073-Load-assembly

Next time, we'll look at wiring up that button to do something interesting.

This is the first part in a series of tutorials aimed at guiding novice developers though building a "Hello World" Windows Home Server Add-In, using only freely-available tools (including Visual Studio Express).

One of the major barriers for beginning developers creating Add-Ins is that the initial setup and configuration of the development environment is fairly convoluted; you need to bring together quite a few components to even make a start. Because of this, Part 1 of our tutorial guides you through the installation and configuration of Visual Studio Express, with some Windows Home Server-specific steps.

It's not glamorous, but it needs to be done!

I'm going to assume you're working with Windows XP, but Windows Vista setup should be basically identical to the steps outlined below.

For this tutorial, we're building a Windows Home Server Add-In that detects changes to Shared Folder permissions. You can access the other parts of the tutorial, or download the code I'm using in the screenshots, using the links below.

  • Part 1: Installing and configuring your development environment
  • Part 2: Designing a GUI and initial deployment test
  • Part 3: Responding to Windows Home Server Notifications (code)
  • Part 4: Using FancyListView and comparing permissions (code)
  • Part 5: Building an installer package using WiX (code)

 

Step 1: Downloading and Installing Visual Studio Express

Jump over to http://www.microsoft.com/express/download/ and download Visual Studio Express for C#. You want the Web Install, so just click Download.

001-Download-VS-Express 002-Install-VS-Express

The installer will pull down all of the various packages you need, install them, and then prompt you to restart your computer. Once you've restarted, Visual Studio will finish up the installation.

003-Install-VS-Express 004-Install-VS-Express

005-Install-VS-Express 006-Install-VS-Express

 

Step 2: Running Visual Studio Express for the first time

Visual Studio Express will spend some time setting up your initial environment. Once loaded, you'll want to register the software so it doesn't expire on you in 30 days. From the Help menu, select Register Product.

007-Run-VS-Express  009-Run-VS-Express

Click the Register Now link, sign in with a Windows Live ID, and complete the registration form.

010-Run-VS-Express 011-Run-VS-Express

Copy the registration code into Visual Studio and click Complete Registration.

012-Run-VS-Express 013-Run-VS-Express

 

Step 3: Download and Install WiX

WiX is a great framework for building MSI packages. You'll need to package your code as an MSI, and customize some settings, in order for Windows Home Server to see it as a valid Add-In to install.

Browse to http://wix.sourceforge.net/ and hit the Weekly Releases section (http://wix.sourceforge.net/releases/).

WiX 3.0 is alpha code, but it's the best option for us at the moment. I've been using WiX 3.0 weekly releases to build my production installers for WHS Disk Management from the start, and it's been rock solid for me.

Navigate to the latest build number, download Wix3.msi and install it.

015-Download-Wix

WiX will complain that you're using Visual Studio Express, but that's expected. WiX won't allow us to use the fancy GUI tools to build packages in Visual Studio Express, but we'll still get command line options. And everyone loves the command line.

016-Download-Wix 017-Download-Wix

 

Step 4: Referencing the Windows Home Server libraries

In order to write code that references Windows Home Server, we need access to the WHS libraries. These will be referenced by Visual Studio, and will allow us to use all the cool WHS GUI bits.

We'll grab these files off our server.

Click Start, Run, type mstsc -console, and then click OK. Enter the name of your server, click the Options button, navigate to the Local Resources tab and then click More. Check the box next to Drives, then click OK.

We're doing this so we can copy and paste files back and forth between our development machine and our server.

018-Connect-to-WHS 018a-Change-RD-settings

Click Connect, and then enter your server's Administrator username and password.

019-Connect-to-WHS  020-Connect-to-WHS

Navigate to C:\Program Files\Windows Home Server\. You now want to select all the Application Extension files; the easiest way to do this is to sort by Type.

I've left off the Xceed libraries here; they're what the console uses to display various charts and graphs, and we don't have a license to use those libraries.

Copy the files (CTRL-C), minimize the Remote Desktop session, and then paste the files (CTRL-V) into a folder on your computer. I've stuck the files in a subfolder of the My Documents\Visual Studio 2008\ folder, but you can put them anywhere.

 021-Save-assemblies 022-Save-assemblies

Brendan Grant has blogged this part of the tutorial already, but I'm including it here for completeness.

Once you've copied the Windows Home Server libraries to your local machine, we need to add a registry entry to tell Visual Studio where to find them. When you add references in Visual Studio, you can manually navigate to the appropriate library, but that gets old fast. This way is easier.

Open regedit and navigate to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v2.0.50727\AssemblyFoldersEx. Right-click AssemblyFoldersEx and create a new Key.

023-Reference-assemblies 024-Reference-assemblies

Call the new key Windows Home Server. Modify the Default entry for your new key, and enter the path to the library files we copied earlier. Click OK to save the path, and then close regedit.

025-Reference-assemblies 026-Reference-assemblies

 027-Reference-assemblies 028-Reference-assemblies

 

Step 5: Adding Windows Home Server controls to the Visual Studio Toolbox

Building Windows Forms applications is always easier when you can use the Visual Studio Designer to drag and drop controls and components. We'll add the Windows Home Server controls to a new Toolbox tab so we can drop them into our Add-In as needed.

Open Visual Studio, and display the Toolbox using the View menu. Right-click the Toolbox, and choose Add Tab.

029-Toolbox-controls 030-Toolbox-controls

Name your new tab Windows Home Server, then right-click the tab and select Choose Items.

031-Toolbox-controls 032-Toolbox-controls

You'll be presented with a window from which you can select various controls. Click the Browse button and navigate to the folder where you saved the Windows Home Server libraries. Select homeservercontrols.dll and click Open.

033-Toolbox-controls 034-Toolbox-controls

Don't touch anything!

All the Windows Home Server controls loaded from homeservercontrols.dll are highlighted, and you don't want to manually go back through that list clicking them all. Just press the space bar once, and they'll all be checked, then click OK.

035-Toolbox-controls 036-Toolbox-controls

 

Step 6: Installing Brendan's Add-In Templates

This is the last step for this part of the tutorial.

Download Brendan's C# templates (http://ihatelinux.blogspot.com/2007/11/whs-developer-tip-4-project-templates.html) and save the zip file to My Documents\Visual Studio 2008\Templates\Project Templates\Visual C#\.

037-Save-templates 

Now reopen Visual Studio and choose File --> New --> Project. You'll see Home Server Add-In as an available template.

039-Open-templates

Your development environment is now ready to begin work on an Add-In. Next time, we'll create a new Add-In from Brendan's template and actually display something in the Windows Home Server Console.

(If you can't wait until then, and decide to go diving in without me, make sure to retarget the project to .NET 2.0; Visual Studio 2008 defaults to .NET 3.5, which isn't installed by default on Windows Home Server.)

Edit: Thanks to Chris and Kevin for pointing out that you just need to save the zip file to the templates folder; extracting the contents of the zip won't work.

Search

Site Sections

Recent Posts

Archives

Post Categories

WHS Add-In Tutorial

WHS Blogs

WHS Development