Welcome to MSDN Blogs Sign in | Join | Help

DDITDev

A crack team of devs in the Developer Division. ASP.NET, SQL, C#, development practices.
LINQ to SQL and multiple result sets in Stored Procedures

In this post I'm going to demonstrate how you would return and consume multiple result sets from a stored procedure in LINQ to SQL.

Imagine you have a stored procedure like this one below. Very simple – it’s just returns all customers and all orders.

clip_image001

In my LINQ to SQL Data Context, I have the following tables.

clip_image002

As well as the stored procedure definition.

clip_image003

Simple enough so far!

The first thing we need to do is create a partial class and define a new method returning type ‘IMultipleResults’. I’ve defined the one below to accept one parameter, which is a customerId.

clip_image004

You’ll notice that there’s a few attributes you need to add.

1. Function – this is the name of the stored procedure that will be called. It’s the same one we defined in our data context above. Note: the actual stored procedure name is irrelevant - it must be the name you have given the stored  procedure on the data context (which will be the same 99% of the time I’d imagine.)

2. ResultType – This is the mapping that basically says “Make the first result type of ‘Customer’ and the second result type of ‘Order’”.

Once we’ve defined this method, we can go ahead and consume it from our UI and/or OM as shown below.

clip_image005

As shown above, you call the method you defined, passing in a customer id in this case, which returns you a type of IMultipleResults. From there it’s just a case of calling the ‘GetResult’ method making sure to pass in the type of the object you want.

The results are shown below!

image

I hope this helps!

Jason

VS2008 Tip - Remove unused 'using' statements

Did you know that VS2008 has a great feature to remove any unused 'using' statements in your code?

Simply right click anywhere in your code file, select 'Organize Usings' and select 'Remove Unused Usings'.

You then end up with only the using statements you are actually using! (no pun intended ;))

Other options include sorting your current using statements, or removing and sorting at the same time..

Hope this helps!

Jason

UPDATE - One of my colleagues just pointed out that you can also do this (as well as some other great stuff) for all files in your project(s) using the PowerCommands for VS2008 add-in. Thanks Rick!

Working with Virtual Machines in PowerShell, SCVMM and C#

Lately I've been experimenting with SCVMM and PowerShell, and I wondered how hard it would be to automate the creation of a Virtual Machine via C#.

Well the good news is that's it not that hard at all!

In this post I'm going to demonstrate how to:

  • Create a new VM
  • Get an existing VM / Get all existing VMs
  • Delete an existing VM

 So let's get started!


Prerequisites

In order to build and run your project, you will need to have the following installed and configured:

You'll also need to have some defined templates in SCVMM, with operating systems installed.


Creating the Application

The first thing you need to do is to create windows forms app (I've called mine "VMManager") and add the required references which can be found in the following folders:

  • C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\
  • C:\Program Files\Microsoft System Center Virtual Machine Manager 2007\bin\

When finished, your list of references should look like this:

Next we need to create a basic UI for testing. Mine looks like this:

Basically, it's just a textbox to enter the name of the new VM we want to create and a listview to display the list of VMs we have created.

Next we need to add a new class to the project, which we'll call 'PowerShellHelper.cs'. This class should look like this:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Management.Automation.Runspaces;
   6:  
   7: namespace VMManager
   8: {
   9:     internal class PowerShellHelper
  10:     {
  11:         public static Runspace InitPowerShell()
  12:         {
  13:             RunspaceConfiguration config = RunspaceConfiguration.Create();
  14:             PSSnapInException ex = null;
  15:  
  16:             config.AddPSSnapIn("VirtualMachineManagerSnapIn", out ex);
  17:             if (ex != null)
  18:                 throw ex;
  19:  
  20:             return RunspaceFactory.CreateRunspace(config);
  21:         }
  22:     }
  23: }

Add another class called 'ExtensionMethods.cs' to contain our (suprise!) Extension Methods. It should look like this:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace VMManager
   7: {
   8:     public static class ExtensionMethods
   9:     {
  10:         public static bool EqualsIgnoreCase(this string s, string stringToCompare)
  11:         {
  12:             return s.Equals(stringToCompare, StringComparison.CurrentCultureIgnoreCase);
  13:         }
  14:     }
  15: }

We also need to add another class, this time calling it 'VMHelper.cs'.  Inside this class we're going to add a fair bit of code, so I'm going to break it down into sections.

First off, we need to add a few private methods to do most of the work for us:

1. GetCommandPipe - This returns a PowerShell command pipeline

   1: /// <summary>
   2: /// Creates a command pipeline
   3: /// </summary>
   4: /// <param name="runspace"></param>
   5: /// <returns></returns>
   6: private Pipeline GetCommandPipe(Runspace runspace)
   7: {
   8:     return runspace.CreatePipeline();
   9: }

 2. GetVM - This queries the SCVMM server and returns an instance of a VM

   1: /// <summary>
   2: /// Gets a VM object by name
   3: /// </summary>
   4: /// <param name="vmName"></param>
   5: /// <param name="runspace"></param>
   6: /// <returns></returns>
   7: private VM GetVM(string vmName, Runspace runspace)
   8: {
   9:     //get the VM
  10:     VM vm = null;
  11:     Command getVM = new Command("get-vm");
  12:     getVM.Parameters.Add("VMMServer", this.VMMServer);
  13:     getVM.Parameters.Add("Name", vmName);
  14:  
  15:     using (Pipeline pipeline = GetCommandPipe(runspace))
  16:     {
  17:         pipeline.Commands.Add(getVM);
  18:         Collection<PSObject> result = pipeline.Invoke();
  19:         vm = (Microsoft.SystemCenter.VirtualMachineManager.VM)result[0].BaseObject;
  20:         pipeline.Stop();
  21:     }
  22:  
  23:     if (vm == null)
  24:         throw new NullReferenceException(string.Format("No VM found with name: {0}", vmName));
  25:  
  26:     return vm;
  27: }

 3. GetProcessorType - This queries the SCVMM server and returns a ProcessorType instance

   1: /// <summary>
   2: /// Gets the processor type
   3: /// </summary>
   4: /// <param name="processorTypeName"></param>
   5: /// <param name="runspace"></param>
   6: /// <returns></returns>
   7: private ProcessorType GetProcessorType(string processorTypeName, Runspace runspace)
   8: {
   9:     //get the ProcessorType
  10:     ProcessorType processorType = null;
  11:     Command getProc = new Command("get-processortype");
  12:     getProc.Parameters.Add("VMMServer", this.m_SCVMMServer);
  13:  
  14:     using (Pipeline pProcType = GetCommandPipe(runspace))
  15:     {
  16:         pProcType.Commands.Add(getProc);
  17:  
  18:         Collection<PSObject> results = pProcType.Invoke();
  19:         if (results.Count == 0)
  20:             throw new NullReferenceException("No proc types found!");
  21:  
  22:         foreach (PSObject item in results)
  23:         {
  24:             ProcessorType p = (ProcessorType)item.BaseObject;
  25:             if (p.Name.EqualsIgnoreCase(processorTypeName))
  26:             {
  27:                 processorType = p;
  28:                 break;
  29:             }
  30:         }
  31:  
  32:         pProcType.Stop();
  33:     }
  34:  
  35:     if (processorType == null)
  36:         throw new NullReferenceException(string.Format("No Processor Type found with for following configuration: {0}.", processorTypeName));
  37:  
  38:     return processorType;
  39: }

 4. CreateHardwareProfile - This creates a new HardwareProfile, containing the hardware configuration for the new VM 

   1: /// <summary>
   2: /// Creates a hardware Profile
   3: /// </summary>
   4: /// <param name="processorType"></param>
   5: /// <param name="ramMB"></param>
   6: /// <param name="runspace"></param>
   7: /// <returns></returns>
   8: private HardwareProfile CreateHardwareProfile(ProcessorType processorType, int ramMB, Runspace runspace)
   9: {
  10:     //create the hardware profile
  11:     Command createHardware = new Command("New-HardwareProfile");
  12:     createHardware.Parameters.Add("VMMServer", this.m_SCVMMServer);
  13:     createHardware.Parameters.Add("owner", @"domain\user"); // must be replaced with a valid SCVMM admin account
  14:     createHardware.Parameters.Add("CPUType", processorType);
  15:     createHardware.Parameters.Add("Name", m_HardwareProfileName);
  16:     createHardware.Parameters.Add("MemoryMB", ramMB);
  17:     createHardware.Parameters.Add("Jobgroup", m_JobGroup);
  18:  
  19:     using (Pipeline pCreateHardware = GetCommandPipe(runspace))
  20:     {
  21:         pCreateHardware.Commands.Add(createHardware);
  22:         pCreateHardware.Invoke();
  23:         pCreateHardware.Stop();
  24:     }
  25:  
  26:     //get the hardware profile
  27:     HardwareProfile hardwareProfile = null;
  28:     Command getHW = new Command("Get-HardwareProfile");
  29:     getHW.Parameters.Add("VMMServer", this.m_SCVMMServer);
  30:  
  31:     using (Pipeline hardwarePipeline = GetCommandPipe(runspace))
  32:     {
  33:         hardwarePipeline.Commands.Add(getHW);
  34:         Collection<PSObject> profiles = hardwarePipeline.Invoke();
  35:         foreach (PSObject item in profiles)
  36:         {
  37:             HardwareProfile hw = (HardwareProfile)item.BaseObject;
  38:             if (hw.Name.EqualsIgnoreCase(m_HardwareProfileName))
  39:             {
  40:                 hardwareProfile = hw;
  41:                 break;
  42:             }
  43:         }
  44:  
  45:         hardwarePipeline.Stop();
  46:     }
  47:  
  48:     if (hardwareProfile == null)
  49:         throw new NullReferenceException(string.Format("No Hardware Profile found with name: {0}.", m_HardwareProfileName));
  50:  
  51:     return hardwareProfile;
  52: }

 5. GetTemplate - Gets a Template object from the SCVMM Server based on the name 

   1: /// <summary>
   2: /// Gets an OS template
   3: /// </summary>
   4: /// <param name="OSTemplate"></param>
   5: /// <param name="runspace"></param>
   6: /// <returns></returns>
   7: private Template GetTemplate(string OSTemplate, Runspace runspace)
   8: {
   9:     // Get a template
  10:     Template template = null;
  11:  
  12:     Command getTemplate = new Command("get-template");
  13:     getTemplate.Parameters.Add("VMMServer", this.m_SCVMMServer);
  14:  
  15:     using (Pipeline pTemplate = GetCommandPipe(runspace))
  16:     {
  17:         pTemplate.Commands.Add(getTemplate);
  18:  
  19:         Collection<PSObject> templates = pTemplate.Invoke();
  20:         foreach (PSObject item in templates)
  21:         {
  22:             Template t = (Template)item.BaseObject;
  23:             if (t.Name.EqualsIgnoreCase(OSTemplate))
  24:             {
  25:                 template = t;
  26:                 break;
  27:             }
  28:         }
  29:  
  30:         pTemplate.Stop();
  31:     }
  32:  
  33:     if (template == null)
  34:         throw new NullReferenceException(string.Format("No Template found with name: {0}.", OSTemplate));
  35:  
  36:     return template;
  37: }

 6. AddVirtualHardDisk - Adds a new virtual hard disk to the server - used for creating a D: drive 

   1: /// <summary>
   2: /// Add a virtual hard disk (D: Drive for example)
   3: /// </summary>
   4: /// <param name="diskSizeGB"></param>
   5: /// <param name="runspace"></param>
   6: private void AddVirtualHardDisk(int diskSizeGB, Runspace runspace)
   7: {
   8:     Command newVirtualHardDisk = new Command("New-VirtualHardDisk");
   9:     newVirtualHardDisk.Parameters.Add("JobGroup", m_VirtualHardDiskJobGroup);
  10:     newVirtualHardDisk.Parameters.Add("Bus", "0");
  11:     newVirtualHardDisk.Parameters.Add("Lun", "1");
  12:     newVirtualHardDisk.Parameters.Add("IDE", true);
  13:     newVirtualHardDisk.Parameters.Add("Dynamic", true);
  14:     newVirtualHardDisk.Parameters.Add("VMMServer", this.m_SCVMMServer);
  15:     newVirtualHardDisk.Parameters.Add("Filename", m_VmName + "_disk_1.vhd");
  16:     newVirtualHardDisk.Parameters.Add("Size", (diskSizeGB * 1024));
  17:  
  18:     using (Pipeline pNewHardDisk = GetCommandPipe(runspace))
  19:     {
  20:         pNewHardDisk.Commands.Add(newVirtualHardDisk);
  21:         pNewHardDisk.Invoke();
  22:         pNewHardDisk.Stop();
  23:     }
  24: }

 7. GetBestHost - This method queries the available hosts managed by SCVMM, and return the host deemed most suitable for the new VM to be created on

   1: /// <summary>
   2: /// Gets the best rated host for the requested vm
   3: /// </summary>
   4: /// <param name="hardwareProfile"></param>
   5: /// <param name="totalHardDiskSize"></param>
   6: /// <param name="runspace"></param>
   7: /// <returns></returns>
   8: private Host GetBestHost(HardwareProfile hardwareProfile, long totalHardDiskSize, Runspace runspace)
   9: {
  10:     string hostname = string.Empty;
  11:  
  12:     //get all host groups
  13:     HostGroup hostGroup = null;
  14:     Command getHostGroup = new Command("Get-VMHostGroup");
  15:     getHostGroup.Parameters.Add("VMMServer", this.m_SCVMMServer);
  16:  
  17:     using (Pipeline hostGroupPipeline = GetCommandPipe(runspace))
  18:     {
  19:         hostGroupPipeline.Commands.Add(getHostGroup);
  20:         Collection<PSObject> foundhostGroups = hostGroupPipeline.Invoke();
  21:         hostGroup = (HostGroup)foundhostGroups[0].BaseObject;
  22:  
  23:         hostGroupPipeline.Stop();
  24:     }
  25:  
  26:     if (hostGroup == null)
  27:         throw new NullReferenceException(string.Format("No Host Group could be found on server: {0}.", this.m_SCVMMServer));
  28:  
  29:     //get vm host ratings
  30:     Command getHostRating = new Command("Get-VMHostRating");
  31:     getHostRating.Parameters.Add("VMHostGroup", hostGroup);
  32:     getHostRating.Parameters.Add("HardwareProfile", hardwareProfile);
  33:     getHostRating.Parameters.Add("VMName", m_VmName);
  34:     getHostRating.Parameters.Add("DiskSpaceGB", totalHardDiskSize);
  35:  
  36:     using (Pipeline hostsPipeline = GetCommandPipe(runspace))
  37:     {
  38:         hostsPipeline.Commands.Add(getHostRating);
  39:         Collection<PSObject> foundhosts = hostsPipeline.Invoke();
  40:  
  41:         foreach (PSObject item in foundhosts)
  42:         {
  43:             ClientObject client = (ClientObject)item.BaseObject;
  44:             hostname = client.Name;
  45:             break;
  46:         }
  47:  
  48:         hostsPipeline.Stop();
  49:     }
  50:  
  51:     Host host = null;
  52:     Command getHost = new Command("Get-VMHost");
  53:     getHost.Parameters.Add("VMMServer", this.m_SCVMMServer);
  54:  
  55:     using (Pipeline hostPipeline = GetCommandPipe(runspace))
  56:     {
  57:         hostPipeline.Commands.Add(getHost);
  58:         Collection<PSObject> hosts = hostPipeline.Invoke();
  59:  
  60:         foreach (PSObject item in hosts)
  61:         {
  62:             Host h = (Host)item.BaseObject;
  63:             if (h.Name == hostname)
  64:             {
  65:                 host = h;
  66:                 break;
  67:             }
  68:         }
  69:  
  70:         hostPipeline.Stop();
  71:     }
  72:  
  73:     if (host == null)
  74:         throw new NullReferenceException(string.Format("No Host could be found with name: {0}.", hostname));
  75:  
  76:     return host;
  77: }

 8. CreateVM - 'Just like it says on the can', it creates the VM!

   1: /// <summary>
   2: /// creates the actual vm
   3: /// </summary>
   4: /// <param name="host"></param>
   5: /// <param name="template"></param>
   6: /// <param name="hardwareProfile"></param>
   7: /// <param name="runspace"></param>
   8: private void CreateVM(Host host, Template template, HardwareProfile hardwareProfile, Runspace runspace)
   9: {
  10:     Command newVM = new Command("new-vm");
  11:     newVM.Parameters.Add("Template", template);
  12:     newVM.Parameters.Add("VMHost", host);
  13:     newVM.Parameters.Add("HardwareProfile", hardwareProfile);
  14:     newVM.Parameters.Add("Name", m_VmName);
  15:     newVM.Parameters.Add("Description", string.Format("VM created by '{0}' on {1}", Environment.UserName, DateTime.Now));
  16:     newVM.Parameters.Add("Path", host.VMPaths[0]);
  17:     newVM.Parameters.Add("Owner", @"domain\user");
  18:     newVM.Parameters.Add("StartVM", true);
  19:     newVM.Parameters.Add("JobGroup", m_VirtualHardDiskJobGroup);
  20:  
  21:     using (Pipeline pNewVM = GetCommandPipe(runspace))
  22:     {
  23:         pNewVM.Commands.Add(newVM);
  24:         pNewVM.Invoke();
  25:         pNewVM.Stop();
  26:  
  27:         if (pNewVM.Error.Count > 0)
  28:             throw new Exception("Could not create vm. Error is: " + pNewVM.Error.ReadToEnd().ToString());
  29:     }
  30: }

 

Next we need to add our public methods. These are the methods we're going to expose to the UI

1. CreateVM - This creates the VM and copies all associated files to the host

   1: public void CreateVM(string vmName, string processorType, string OSTemplate, int ramMB, int diskSizeGB)
   2: {
   3:     // Code to actually create a VM
   4:     using (Runspace runspace = PowerShellHelper.InitPowerShell())
   5:     {
   6:         m_VmName = vmName;
   7:         m_JobGroup = Guid.NewGuid().ToString();
   8:         m_HardwareProfileName = "Profile" + Guid.NewGuid().ToString();
   9:         m_VirtualHardDiskJobGroup = Guid.NewGuid().ToString();
  10:  
  11:         try
  12:         {
  13:             runspace.Open();
  14:  
  15:             //get processor type
  16:             ProcessorType processor = GetProcessorType(processorType, runspace);
  17:  
  18:             //create the hardware profile
  19:             HardwareProfile hardwareProfile = CreateHardwareProfile(processor, ramMB, runspace);
  20:  
  21:             //get the template
  22:             Template template = GetTemplate(OSTemplate, runspace);
  23:  
  24:             //add the extra virtual hard disk (D: Drive)
  25:             AddVirtualHardDisk(diskSizeGB, runspace);
  26:  
  27:             //get the total hard disk space requested
  28:             double virtualHardDiskSize = 0;
  29:  
  30:             //loop through each virtual hard disk in the template and add the disk size
  31:             foreach (VirtualHardDisk hardDisk in template.VirtualHardDisks)
  32:                 virtualHardDiskSize += (((double)hardDisk.Size) / 1024 / 1024 / 1024);
  33:  
  34:             //add the extra virtual hard disk (d: drive?)
  35:             virtualHardDiskSize += (diskSizeGB);
  36:             virtualHardDiskSize = Math.Ceiling(virtualHardDiskSize);
  37:  
  38:             //get the best host
  39:             Host host = GetBestHost(hardwareProfile, (long)virtualHardDiskSize, runspace);
  40:  
  41:             //create the vm
  42:             CreateVM(host, template, hardwareProfile, runspace);
  43:         }
  44:         catch (Exception ex)
  45:         {
  46:             //todo: additional logic here?
  47:             throw ex;
  48:         }
  49:         finally
  50:         {
  51:             //close the runspace
  52:             runspace.Close();
  53:         }
  54:     }
  55: }

2. Delete VM - Deletes the VM from the host and removes all associated files.

   1: public void DeleteVM(string vmName)
   2: {
   3:     // Code to actually delete a VM
   4:     using (Runspace runspace = PowerShellHelper.InitPowerShell())
   5:     {
   6:         try
   7:         {
   8:             runspace.Open();
   9:  
  10:             //get the VM
  11:             VM vm = GetVM(vmName, runspace);
  12:  
  13:             if (vm != null)
  14:             {
  15:                 //you cannot delete a vm that is running.
  16:                 //loop whilst the vm is not powered off (stopped)
  17:                 int loopCount = 1; //max loop count = 12 - try for 1 minute only
  18:                 while (vm.Status != VMComputerSystemState.PowerOff && loopCount <= 12)
  19:                 {
  20:                     Command stopVM = new Command("Stop-VM");
  21:                     stopVM.Parameters.Add("VM", vm);
  22:  
  23:                     using (Pipeline pStop = GetCommandPipe(runspace))
  24:                     {
  25:                         pStop.Commands.Add(stopVM);
  26:                         pStop.Invoke();
  27:                         pStop.Stop();
  28:                     }
  29:  
  30:                     //sleep for 5 seconds and check the status again
  31:                     Thread.Sleep(5000);
  32:  
  33:                     ///append the loop count
  34:                     loopCount++;
  35:                 }
  36:  
  37:                 //delete the vm
  38:                 Command deleteVM = new Command("Remove-VM");
  39:                 deleteVM.Parameters.Add("VM", vm);
  40:  
  41:                 using (Pipeline pDelete = GetCommandPipe(runspace))
  42:                 {
  43:                     pDelete.Commands.Add(deleteVM);
  44:                     pDelete.Invoke();
  45:                     pDelete.Stop();
  46:                 }
  47:             }
  48:         }
  49:         catch (Exception ex)
  50:         {
  51:             //todo: additional logic here?
  52:             throw ex;
  53:         }
  54:         finally
  55:         {
  56:             //close the runspace
  57:             runspace.Close();
  58:         }
  59:     }
  60: }


Creating the VM

Now that we've got our classes defined, let's add some code to the application to call the public methods we defined earlier and create our VM!

Modify the click event of your button to include the following code: (NOTE: you will have to modify line 5 to match the Template you have created. The list of processor Types are available in SCVMM - I have merely selected one for the purposes of this article, but you can choose whichever one that best suits your needs)

   1: private void btnCreateVM_Click(object sender, EventArgs e)
   2: {
   3:     string vmName = txtVMName.Text;
   4:     string processorType = "1-processor 1.80 GHz Pentium 4";
   5:     string osTemplate = "Win Server 2003";
   6:     int ramMB = 512;
   7:     int diskSizeGB = 10;
   8:  
   9:     VMHelper helper = new VMHelper("your_SCVMM_server");
  10:     helper.CreateVM(vmName, processorType, osTemplate, ramMB, diskSizeGB);
  11: }

Compile the solution and run the app (making sure you've updated the code to reflect your SCVMM server and domain accounts. If all goes well you should see your VM bring created in SCVMM!


Getting all existing VMs

Modify the 'VMHelper.cs' class to include the following methods:

   1: /// <summary>
   2: /// Gets a list of VMs
   3: /// </summary>
   4: /// <param name="runspace"></param>
   5: /// <returns></returns>
   6: private List<VM> GetVMs(Runspace runspace)
   7: {
   8:     List<VM> vms = new List<VM>();
   9:  
  10:     //get the VM
  11:     VM vm = null;
  12:     Command getVM = new Command("get-vm");
  13:     getVM.Parameters.Add("VMMServer", this.m_SCVMMServer);
  14:  
  15:     using (Pipeline pipeline = GetCommandPipe(runspace))
  16:     {
  17:         pipeline.Commands.Add(getVM);
  18:         Collection<PSObject> result = pipeline.Invoke();
  19:         foreach (PSObject item in result)
  20:         {
  21:             vms.Add((VM)item.BaseObject);
  22:         }
  23:         
  24:         pipeline.Stop();
  25:     }
  26:  
  27:     return vms;
  28: }

 

   1: public List<VM> GetVMs()
   2: {
   3:     using (Runspace runspace = PowerShellHelper.InitPowerShell())
   4:     {
   5:         try
   6:         {
   7:             runspace.Open();
   8:  
   9:             return GetVMs(runspace);
  10:         }
  11:         catch (Exception ex)
  12:         {
  13:             //todo: additional logic here?
  14:             throw ex;
  15:         }
  16:         finally
  17:         {
  18:             //close the runspace
  19:             runspace.Close();
  20:         }
  21:     }
  22:     
  23:     return null;
  24: }

Now we need to modify the UI. Add an additional method called GetVMs:

   1: private void GetVMs()
   2: {
   3:     listView1.Items.Clear();
   4:     listView1.View = View.Details;
   5:  
   6:     listView1.Columns.Add("Name");
   7:     listView1.Columns.Add("Status");
   8:  
   9:     VMHelper helper = new VMHelper("your_SCVMM_server");
  10:     List<VM> vms = helper.GetVMs();
  11:     
  12:     foreach (VM vm in vms)
  13:     {
  14:         ListViewItem lvi = new ListViewItem(vm.Name);
  15:         lvi.SubItems.Add(vm.Status.ToString());
  16:  
  17:         listView1.Items.Add(lvi);
  18:     }
  19: }

Then modify the form_load event to call the GetVMs() method:

   1: private void Form1_Load(object sender, EventArgs e)
   2: {
   3:     GetVMs();
   4: }

Also, add the call to GetVMs to the button click event. This will cause the list to refresh once the VM is created.

Run your app and you should see a list of the existing VMs! :)


Deleting an existing VM

Deleting an existing VM is quite straightforward. We simply need to pass the name of the VM to remove and issue the command.

Let's add another button to the form called 'btnDelete'. Your form should look similar to this:

 Add the following code to the click event of btnDelete: (NOTE: you will need to modify the server name in the VMHelper constructor - line 7)

   1: private void btnDelete_Click(object sender, EventArgs e)
   2: {
   3:     if (listView1.SelectedItems.Count > 0)
   4:     {
   5:         string vmName = listView1.SelectedItems[0].Text;
   6:  
   7:         VMHelper helper = new VMHelper("your_SCVMM_server");
   8:         helper.DeleteVM(vmName);
   9:  
  10:         GetVMs();
  11:     }           
  12: }

 Now run the app. To delete a VM, simply select it in the list view and click the delete button!


Conclusion

By combining PowerShell with SCVMM, automating the creation of Virtual Machines becomes fairly straightforward. Mix in C#, and you have an excellent platform to build upon.

I hope this article has given you some good insights and enables you to get started building VM tools with these great technologies.

Jason

Creating Arcs and Changing Centers with Expression

I know I had a hard time trying to work this out when I was starting to use Expression, so I thought it might be useful to share! Thanks Tim!

Creating Arcs and Changing Centers with Expression

Jason

A Real-Time Trace Listener/Watcher
Goals of this post:
  • Example for writing a custom trace listener which outputs real-time information over the network, which can then be consumed by a remote client.
  • Example for writing that remote client, which functions much like FileMon in showing real-time data from the trace listener:

The whole solution is attached to the post. Written [hurriedly] in VS2008 Beta2, but it will probably work just fine in VS2005. It's no where near pristine code; more a proof-of-concept, so take what you will from it.

The Problem:

One of the apps we developed internally consists of many instances of a service running on multiple machines, talking to many external services and then to a central DB, and doing so at a very high frequency with many threads.

We find that it's difficult to profile and debug such services due to both the complexity introduced by the multiple threads, and due to the fact that it's difficult to replicate the behavior in a test environment since the live one offered so much more data and opportunity for failure.

What we ended up doing in the latest incarnation was throwing in Trace.Write() statements everywhere, to allow the service to communicate not only what it was currently doing (as in the traditional "printf debugging"), but to also throw in occasional useful measures (as in a standard windows performance counters).

The problem is, how do we view these traces? The built-in eventlog and logfile trace listeners weren't suitable for us because they inundate their respective targets with an ever-increasing amount of data; which requires maintenance.

The Solution:

What we wanted was something exactly like the SQL Profiler, or ProcMon (previously "FileMon" + "RegMon"), something that would let us view what's happening as it happens, without being too intrusive.

The solution consists of two parts:

  1. The LiveTraceListener. This is a very simple class derived from the built-in TraceListener, but instead of writing to a file or to the eventlog, it writes to a network port.
  2. The LiveTraceViewer. This is a Winforms application that can connect to the above listener, read from that network port, and output the data to the screen.

The Listener

The listener is fairly simple. All you need to do in order to implement a custom listener is inherit from the System.Diagnostics.TraceListener class, then implement the Write() and WriteLine() methods. Here is a good article explaining it.

In this particular case, the complication comes in the constructor, where we perform some intialization to bind to the TCP port and get a stream to write to.

In the Write() methods, we check if there's someone connected to us. If there is, we write the string to them. If not, we discard it.

This way, we have a lightweight tracing solution that only really does anything when you're watching. Dare I say, a Schrödinger Listener? :)

The Viewer

The core of the viewer is just opening a TCP connection and reading strings from it. Everything else is window dressing. It could be a console app, an MMC plugin, a WPF app - it really doesn't matter.

What could be done better?

  • Well, for one, there are nicer ways to serialize data over the wire instead of reading/writing individual bytes over a TCP port. I was just in a hurry, and I happened to be comfortable with this method :)
    • Ideally, it would be changed to use proper serialization, so we could send trace events with more data, such as severity.
  • Both sides of the solution could use better error checking/handling.
  • It really should be an MMC plugin, so that we could monitor multiple machines concurrently with ease, and because it's the Right Thing To Do.

I'm sure there's plenty more that could be done to this to make it more useful. If you have suggestions, feel free to post them here as comments.

Avi

Using Virtual Earth in your WPF application
Technorati Tags: , , ,

I wanted to experiment more with WPF, since I don't get to use it in my Real Job. Also, I love me some Virtual Earth, so I was looking to find a way to merge the two. What I came up with is an application which would allow you to tag your photos with geo-location information.

One of the difficulties was with this combination of VE and WPF; it's not an outta-the-box scenario, but it's definitely doable. The crux of the issue is that VE is a web application - it relies on HTML and AJAX to do its magic - but WPF doesn't ship with a full web browser control.

This dilemma is solved by the fact that WPF does support embedding standard Winforms controls, and Winforms does in fact ship with a web browser control.

Aside from that, the only other interesting issue I faced was that for this application to work, I needed two-way communication between the JavaScript running in the browser, and the C# code in my WPF app. Turns out this is also doable :)

I'll divide the rest of the post into the logical parts that I had to deal with.

Creating the Winforms control

This is the easy part. Add a project to your solution of type "Windows Forms Control Library". You'll get your typical Winforms canvas to play with, onto which you drag a WebBrowser control from the toolbox. That's essentially it for your control - it's just a web browser inside a container. You will need to call a few methods on the WebBrowser control from your WPF code. To do this, you can either wrap those methods in your user control, or directly expose the WebBrowser control as a property of your user control:

   1: public WebBrowser WebBrowserControl
   2: {
   3:     get { return browserMain; }
   4: }
Embedding the Winforms user control in your WPF app

In order to embed this user control, you'll need to do a few things in your WPF project:

  • Add a reference to System.Windows.Forms
  • Add a reference to WindowsFormsIntegration
  • Add a reference to the new user control project you created.

Once that's done, you can add a WPF WindowsFormsHost, which is a control that hosts Winforms controls. In your XAML, throw this where you want the map to appear:

   1: <WindowsFormsHost x:Name="hostWinForms" />
 
Now notice that this control is somewhat empty - nowhere is your user control mentioned. The WindowsFormsHost is just a husk in which you need to put your custom control. So, let's give this host control a child:
 
   1: browserControl = new WinFormsWebBrowserControl.WebBrowser();
   2: hostWinForms.Child = browserControl;
That's it. Now you can treat this browserControl object like a normal WPF control, and it will do your evil bidding. Let's talk more about that...
Getting the Winforms control to show a map

If you've ever done anything with VE in the browser before, you'll know this is a piece of cake. To get it working in this scenario, you write an HTML page just like you would for a normal website, and then you set the URL of the WebBrowser to point to this file. For example:

   1: browserControl.WebBrowserControl.Url = new Uri("VE.htm");
 
(I recommend getting the explicit path to the HTML file, since you can never trust what environment you'll be in. Better yet, perhaps embed it as a resource).
 
For the actual contents of VE.htm, see the interactive SDK for a working sample. It's really a trivial HTML page.
Talking from WPF to JavaScript

My geo-tagging app needed to be able to give various commands to the map. For example, telling the map div to resize in response to the WPF window resizing, or adding a push-pin. The concept is actually very simple: You can execute any JavaScript command you want, just by calling the WebBrowser.Document.InvokeScript method.

So the map resize functionality consisted of a JavaScript method in my VE.htm page:

   1: function Resize(width, height)
   2: {
   3:     map.Resize(width, height);
   4: }

(The map object is the typical one created by the call to the VEMap() constructor)

... Which was called from WPF like this:

   1: void hostWinForms_SizeChanged(object sender, SizeChangedEventArgs e)
   2: {
   3:     browserControl.WebBrowserControl.Document.InvokeScript("Resize", browserControl.Width, browserControl.Height);
   4: }

I'm basically telling the map to resize to the size of my user control. Notice how the arguments are passed through without casting or anything weird. It Just Works.

Talking from JavaScript to WPF

This is the final bit and a little bit more complex - but not much harder to implement. Javascript supports a system to call methods in the window that's hosting it. This is done using window.external.<method name>.

The catch is twofold:

  • You need to tell the browser control where to direct these method calls to.
  • The class that accepts these method calls need to be marked with an attribute that allows it to be visible to COM classes (which the browser control is).

Here's the minor issue: It's tempting to just make your main WPF page the go-to object and have it handle all the calls. The problem is that because it inherits from Window, it can't take this attribute.

We get around this by making an intermediate WPF class that can take the attribute and that knows about your main window class. This object can then call methods in your main window. Something like this:

   1: [ComVisible(true)]
   2: public class ObjectForScriptingHelper
   3: {
   4:     MainWindow m_Window;
   5:  
   6:     public ObjectForScriptingHelper(MainWindow w)
   7:     {
   8:         m_Window = w;
   9:     }
  10:  
  11:     public void MapPositionChange(double lat, double lon, int zoom)
  12:     {
  13:         m_Window.MapPositionChanged(lat, lon, zoom);
  14:     }
  15: }

Looking at the above, we have two methods:

  • Constructor: Takes an instance of my MainWindow class, and stores it for later reference.
  • MapPositionChange: This is a method which will be called by the JavaScript on the VE.htm page whenever the map wants to inform us that the user changed the map location. When this happens, this method calls a corresponding method in the MainWindow class, which simply records the Latitude/Longitude.

So the calling path is like this:

    JavaScript --> ObjectForScriptingHelper instance --> MainWindow instance.

How do we tell the JavaScript to call the ObjectForScriptingHelper class, rather than just the user control that's hosting the WebBrowser control? Like this, in our MainWindow constructor:

   1: ScriptHelper = new ObjectForScriptingHelper(this);
   2: browserControl.WebBrowserControl.ObjectForScripting = ScriptHelper;
Conclusion

That's it! Once you have this framework set up, you can add method calls back/forth between the VE.htm page and the WPF code. So it's a little complex, but there's no major code to be written. Once you get the concept it's not too bad - and the end result is gorgeous.

Avi

Speeding up image loading in WPF using thumbnails

During a recent WPF session I needed to build a ListBox that showed a bunch of images loaded from an arbitrary directory. Thanks to WPF's data binding, this was trivial - I just needed to get a collection of objects that each had a property pointing to the full path of the image, and WPF would take care of all the loading/displaying. Something like this:

   1: <ListView ItemsSource="{Binding}">
   2:     <ListView.ItemTemplate>
   3:         <DataTemplate>
   4:             <Image Source="{Binding Path=FullPath}" />
   5:         </DataTemplate>
   6:     </ListView.ItemTemplate>
   7: </ListView>

The assumption here is that the DataContext for this window is set to a collection of "Photo" objects. The Photo class has a member called "FullPath" which is just a string with the full path of the photo on disk - this is what the Image.Source member expects.

This worked, but it didn't take long to see a major issue: With today's cameras, loading multiple 5+ megapixel images could take a while (not to mention the RAM requirements).

After a little digging, I found a solution. There exists a feature in the BitmapImage class that allows you to load an image but tell it to only load a thumbnail. To use this, you have to step out of the shrink-wrapped data binding world and insert a converter into the equation. Basically, this converter will take the above string with the full image path, it will load the image (as a thumbnail), and pass it back into the Image.Source parameter as a BitmapImage, which it's happy to consume.

First, let's look at this converter's code and how it loads the thumbnail:

   1: public class UriToBitmapConverter : IValueConverter
   2: {
   3:     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   4:     {
   5:         BitmapImage bi = new BitmapImage();
   6:         bi.BeginInit();
   7:         bi.DecodePixelWidth = 100;
   8:         bi.CacheOption = BitmapCacheOption.OnLoad;
   9:         bi.UriSource = new Uri( value.ToString() );
  10:         bi.EndInit();
  11:         return bi;
  12:     }
  13:  
  14:     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  15:     {
  16:         throw new Exception("The method or operation is not implemented.");
  17:     }
  18: }

Notice line #7. That's the magic line which tells it how big of a thumbnail to load. The smaller the number, the quicker the load, the lower the quality. Notice also line #8 - this is there to force the image file to be closed after it's loaded. Without that, I found that my app couldn't write back to the image file since the ListBox still had it open.

Next, let's look at the XAML change to insert this converter into the mix. You'll need to create a resource for it:

   1: <Window.Resources>
   2:     <local:UriToBitmapConverter x:Key="UriToBitmapConverter" />
   3: </Window.Resources>

The "local:" namespace directive on line #2 is one I'd made sure to add to my main "Window" declaration, like this (line #4):

   1: <Window x:Class="MyClass.Demo"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:local="clr-namespace:MyClass"
   5:     Title="Demo" Height="300" Width="300">

Lastly, use this new resource in the Image element so that FullPath (a string) gets pushed through the converter before Image.Source gets it:

   1: <Image Source="{Binding Path=FullPath, Converter={StaticResource UriToBitmapConverter}}" />

That's it. Your images will now load very quickly. Tweak the thumbnail size to vary the speed versus quality.

Avi

SQL Server Management Studio - Quick Tip

Just a quick tip I thought I would share...

When working in SQL Server Management Studio, an easy way to hide the results of a query is to press CTRL + R. Press it again to toggle showing the results.

Jason

Quality & Trust

Recently we had a new release of an application that didn't go as well as planned, which I'm sure a lot of you can probably relate to - after all, how many rollouts do go as planned?

This new release contained miles of bug fixes and enhancements and was being touted as a major step forward. Lots of work had been put into making this release "a fresh start", enabling an excellent foundation for us to build on in future versions - something the previous versions were lacking.

So from a development point of view this new release was a great success.

However, even though we (the developers) were pleased, once we rolled it out we began to notice a few problems, mainly related to the different environments (dev, test, live). You know - the errors that seem to "work fine on my machine" but crash horribly on the server.

Whilst we have since resolved the errors and the application is now working great, the questions remain - did we take a step forward in the eyes of the user? Will they overcome the inconvenience we caused them during the upgrade now that they see the benefits of the new version? etc, etc

Yes, I think so. BUT - we have tarnished a fragile relationship. The relationship the users have with us. They depend on the application to work, and work well, hence they depend on us. They trust us. They trust us to make sure each new version is better than the last. They trust us to make sure every release is smooth and seamless. They trust us to make sure we performed in-depth testing.

So did we break that trust? Did we deliver the quality the users have the right to expect?

I hope not, but if we did - trust is something that's going to take a while to rebuild - perhaps a lot longer than it will take to rebuild the application alone.

Jason

Windows Live Writer Beta 2

If you've read my earlier post, Windows Live Writer Plugins & Dirty Diapers!, you'll know that I'm a big fan of Windows Live Writer.

Well good news! They've just released Beta 2 and it just keeps getting better.

You can check it out and download it here.

- Jason

What do you want? ;)

Yesterday my manager sent an email around to the team asking for ways we could better utilize the blog and how we could 'do more blogging'.

Needless to say, his email got me thinking...

How do we - an internally facing team - better connect with customers? What sort of stuff should we write about which benefits you?

So much of what gets blogged about seems to be regurgitated information. How do we create original, yet thought provoking posts?

I know I've got a few ideas, and hopefully you should start to see them come out in the next few weeks, but I'd really like your input.

What would you like us to write about? What sort of things as a web developer would you be interested in knowing more about?

Let us know and we'll see what we can do!

- Jason

Easiest. Hire. Ever.

One of the team members pointed out yesterday that it's exactly a year since I made this obnoxious post about how difficult it is to find good people.

That post was made after about two months of trying to find two temp employees, if I remember correctly, and I was somewhat frustrated :)

Anyway, the interesting thing about that post is that it worked! The employee who pointed out the age of the post(Jason) was one of the few who responded to it, and has been working here for 7 months now. He actually got offers from two teams at MS when he came to interview, but the pub-crawling skills of my boss managed to convince him to go with us. Sucker.

Normally it takes alot more effort than that to find good people, so I highly recommend trying something like this.

Avi

 

Reflector 5.0 released!

 

If you're serious about developing in .NET, you MUST have this tool!

Scott Hanselman has a great post detailing the new features here:

http://www.hanselman.com/blog/Reflector5ReleasedWorldDominationAssured.aspx

You can download it from here

- Jason

Windows Live Writer Plugins & Dirty Diapers!

I've been playing around with Windows Live Writer for a little while now (since it first came out in beta!) and the more I use it, the better it seems to get (in case you haven't used it, I encourage you to check it out).

The only "problem" I've had so far is my lack of understanding on how to create plugins for it. Well my friends - I've worked it out and I intend to share it with you now!

Oh! Before I go any further, let me explain the title of this post...

I've been wanting to write this post for a little while now, but this is the first chance I've had to sit down and have some "time to myself". My wife and I welcomed our new baby daughter into the world a few weeks ago and since then my world has been turned upside down! When I'm not changing diapers, or bonding my her and my wife, I like to try and catch up on the little sleep I seem to be getting at the moment! But just for you (OK.. and me!) I have managed to escape to my office for a while and finally get this article "out the door"... ;)

Anyway, let's get going!

The first step to writing a plugin was working out what I wanted it to do. I finally settled on creating a plugin to insert a thumbnail image of any given web page and have it appear as an image in your blog post.

I was going to write this post about how you create a plugin yourself, but I thought the best way to show you would be to provide the source code for my plugin!

A few points worth mentioning...

  1. When creating a plugin, make sure you add a reference to the WindowsLive.Writer.Api.dll file located in the Program Files\Windows Live Writer directory.
  2. Make sure you add attributes to your class describing the plugin. This is how Windows Live Writer "learns" about your plugin. Mine looks like this:

[InsertableContentSource("Website Image"),

                  WriterPlugin("32a77b7a-5ea4-47be-84fc-ff7471384e10",

                  "InsertWebsiteImagePlugin",

                  ImagePath = "Web.png",

                  PublisherUrl = "http://blogs.msdn.com/jasonward/",

                  Description = "Inserts a thumbnail image of a website into the Live Writer Window"),

                  LiveClipboardContentSource("html", "text/html"),

                  UrlContentSource("http://(.*)",

                  ProgressCaption = "Capturing Website Image..",

                  RequiresProgress = true)]

    public class InsertWebsiteImage : SmartContentSource

So what's the plugin called and where can I get it?

It's called "Insert Website Image" and you can download it from here!

All source code can also be downloaded from here!

Hope this helps,
Jason

p.s - I've hosted all the source code on codeplex, so if you'd like to contribute to this plugin or perhaps even add your own plugin to this project, please let me know!

Windows Services Or Task Scheduler? - Part I

Before I even start I’m sure all you know what I’m going to talk about. First let me explain what our team does in Microsoft.

Ours is an automation team whose primary function is develop apps  for the Developer Division build lab for managing the build process, track issues, monitor the servers, provide statistics on various parameters . Or in short, basically manage their day-to-day activities. All our apps are web based (for obvious reasons) hosted on multiple web servers connecting to multiple database instances.  We use ASP.NET 1.1 & 2.0 with C# and SQL 2005 databases. We have a number of jobs for doing some tasks like updating databases, send notifications to users etc... So for each task we wrote a console app in C# and scheduled via Windows Task Scheduler for various intervals. Roughly we have around 50+ web sites and 70+ scheduled tasks and services.

Some of the problems we faced were

1)      Deployment

 

a.       Most of our business logic was embedded in the web sites so we had to distribute the components to the console app as they were hosted in different servers. So every time we made a change to those components we had to make sure that we updated all the servers running our tasks. We minimized this by writing web services in the same web site and calling the web services from the console app. Not a clean solution though.

 

b.      We used a single service account to access our web services and databases. So whenever we created a task in the scheduler we had to provide its credentials. Everytime the password changes we had to manually update all our tasks which is tedious and error prone.  Recently we got some script which will update the credentials for all the tasks.

 

2)      Maintenance – As we started creating console app for each task we ended up having 70+ tasks and growing which became a maintenance nightmare.  One way to minimize this is by having a single console app with different command line parameters. Truly I’m not crazy about this solution.

 

3)      Error Reporting – There is no way we could get any notifications when a task fails. We had to implement exception handling in each app and notify us via email in case of failure. In some cases we also wanted to know successes to make sure that critical tasks are running fine etc…

 

4)      Access Control – Windows Scheduler requires an admin account to setup various tasks. This is not very practical for our team as we also host some apps for other teams.  For security reasons we cannot grant admin access to all of them to our servers.  So every time they want to add/modify a task they had to notify us and we did it for them.  Also as we added new tasks it became hard for us to differentiate between our teams tasks from them as windows scheduler doesn’t provide any kind of grouping of tasks. So we had to rely on the naming conventions for the tasks.

 

5)      Statistics – For some specialized tasks we wanted to know the duration, return values etc.. Over time which will help us to monitor the tasks and fine tune them if required.  This will also be useful to decide if we need to change the run interval or to run in multiple servers etc..

 

We decided windows services as our solution and wrote a number of windows services one for each associated web site and deployed them.  Whenever we wanted a new task we added a timer and executed code in the timer event.  Though it solved a few of the above mentioned problems like high frequency tasks, management via mmc etc.. we had to write code for setting and managing the timer, exception handling, reporting etc..

So I set out to improve the situation and find a solution which will get best of the both worlds plus catering much more to the requirements mentioned above. I started finding information from the internet and came across some interesting articles like the ones below

Jon Galloway’s blog – http://weblogs.asp.net/jgalloway/archive/2005/10/24/428303.aspx

Jon says windows services should not be used to run interval based tasks and that windows scheduler should be used instead. But later he agrees that in some cases windows services are more suitable. As I already explained windows scheduler is not suitable for our team.

MSDN Magazine – http://msdn.microsoft.com/msdnmag/issues/05/03/SchedulingASPNETCode/

All these solutions address specific needs but not all of our requirements. So I decided to write a task scheduler from scratch as a windows services using C#.  In addition to addressing all the above mentioned problems it should additional features which we will see in a while.

Due to its complexity I decided to develop the service in multiple phases addressing the most critical requirements to nice to haves. (But later we became content with the first few phases and never cared to spend time to develop the rest of the features J. I’ll do when I get time)

Phase I

1)      Provide basic task scheduling with intervals based on minutes/hours/daily/weekly/monthly

2)      Provide option to have multiple schedules for same task. For. e.g. have a task run at 8am and 8pm everyday and also once at midnight every 1st day of a month etc..

3)      Provide ability to schedule different types of tasks like Web Service, SQL StoredProc, Custom .NET code etc…

4)      Allow configurable parameters for each tasks. This could be parameters for the web service, SQL sproc etc.

5)      Provide a simple UI to manage the individual tasks.

6)      Have ability to enable/disable an individual task, set timeouts etc.. without having to restart the scheduler.

7)      Provide basic notifications in case of failures.

8)      Provide ability to have multiple task schedulers

 

The scheduler framework will be implemented as a single class which we named it as DDRTLib.Windows.Services.ServiceBase which derives from System.ServiceProcess.ServiceBase. A developer can simply create a new .NET windows service project and replace the default base class to the this class. No additional code is required at all. This base class defines a single timer which is set to trigger every minute. When the timer event is triggered the framework will loop through all the tasks to be run and spans a new thread from a thread pool to execute the task.

 

 Since we will be defining all our schedules via config file the config section must be registered as a custom section. The whole config section will be handled by a class called DDRTLib.Windows.Services.ScheduledTaskController

 

Here’s a sample config file

<configuration>

  <configSections>

    <section name="scheduledTasks"

    type="DDRTLib.Windows.Services.ScheduledTaskController, DDRTLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6509eb956bdc2c34, Custom=null"/>

  </configSections>

  <scheduledTasks>

            <task

        name="Daily Summary Mail"

        type="Custom"

              customType="MyNamespace.CustomTask, MyTasks"

        enabled="true"

              timeout="5"

> 

      <schedule type="Interval " interval="15" />

            <schedule type="Daily " days="Sunday, Monday" startTime="17:20"/>

<schedule type="Monthly" dates="1,2,3" months="March, April" startTime="17:20"/>

      <settings>

              <add key="server" value="MyProdServer"/>

        <add key="email" value="karthip@microsoft.com" />

            </settings>

    </task>

 

    <task

              name="SanMan Data Cache Refresh"

        type="WebService"

              webServiceUrl="http://webserver/webservice.asmx"

        webMethod="RefreshSanManData"

              enabled="false"

        timeout="60"

            >

      <schedule type="Interval" interval="5"/>

      <settings>

            </settings>

    </task>

    <task

        name="TeamStats Queue Health"

              type="StoredProcedure"

              storedProcName="up_GetDataPointQueueSummary"

        connectionString="myconnectionstring"

              enabled="true"

        timeout="5"

            >

      <schedule type="Interval" interval="10"/>

            <settings>

        <add key="@DataPointTypeId" value="1"/>

              <add key="@SaveResults" value="1"/>

      </settings>

    </task>

  </scheduledTasks>

</configuration>

Note that the above config file does not show the custom sections for the exception management block which our framework uses to publish the exceptions to event log, email etc..

 The framework also sets up a file watcher on the config file so that any changes to it will be effected immediately.  This allows us to share this config file over network and grant access to appropriate users. If the config file is ill-formed or incomplete the service shuts itself down and notifies the users.  If we can also have it notify the users and use previous configuration settings.

 We’ve been using this framework for the last 6 months slowly moving all our existing  scheduled tasks and services and it works like a charm and saves us from a lot of headache. In my next blog I’ll explain the next phases along with some code excerpts.

Happy Programming J.

Karthik Palanivel

More Posts Next page »
Page view tracker